feat: wi-fi - join WPA2/WPA3-PSK networks and carry traffic like ethernet

This commit is contained in:
2026-08-06 19:44:35 +02:00
parent a01e63c717
commit bbe1df62fd
40 changed files with 5878 additions and 272 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 53
#define MONTAUK_BUILD_NUMBER 69
+6
View File
@@ -13,6 +13,7 @@
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Drivers/Net/Wifi/Wifi.hpp>
#include <Drivers/Graphics/IntelGPU.hpp>
#include <Drivers/Storage/Ahci.hpp>
#include <Drivers/Audio/IntelHda.hpp>
@@ -127,6 +128,11 @@ namespace montauk::abi {
if (Drivers::Net::E1000E::IsInitialized()) {
add(5, "Intel E1000E", "Gigabit Ethernet (82574L)");
}
if (Drivers::Net::Wifi::IsPresent()) {
add(5, "Intel Wi-Fi", Drivers::Net::Wifi::IsLinkUp()
? "802.11 wireless (connected)"
: "802.11 wireless");
}
// Display (category 6)
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
+22 -11
View File
@@ -15,8 +15,10 @@
#include <Net/Ipv4.hpp>
#include <Net/Socket.hpp>
#include <Net/NetConfig.hpp>
#include <Net/NetIf.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Drivers/Net/Wifi/Wifi.hpp>
#include "Syscall.hpp"
@@ -96,12 +98,9 @@ namespace montauk::abi {
out->subnetMask = Net::GetSubnetMask();
out->gateway = Net::GetGateway();
const uint8_t* mac = nullptr;
if (Drivers::Net::E1000::IsInitialized()) {
mac = Drivers::Net::E1000::GetMacAddress();
} else if (Drivers::Net::E1000E::IsInitialized()) {
mac = Drivers::Net::E1000E::GetMacAddress();
}
// Whichever interface is carrying traffic; on a Wi-Fi link this is the
// adapter's MAC, not an idle Ethernet port's.
const uint8_t* mac = ::Net::NetIf::ActiveMac();
if (mac) {
for (int i = 0; i < 6; i++) out->macAddress[i] = mac[i];
} else {
@@ -122,6 +121,9 @@ namespace montauk::abi {
dst[i] = '\0';
}
// Reports whichever interface is currently carrying traffic, so a machine
// that joined a Wi-Fi network sees the wireless counters here rather than
// an idle Ethernet port.
static void Sys_NetStatus(NetStatus* out) {
if (out == nullptr) return;
@@ -133,15 +135,24 @@ namespace montauk::abi {
out->txPackets = 0;
CopyNetStatusDriver(out->driver, "No adapter");
if (Drivers::Net::E1000::IsInitialized()) {
out->initialized = 1;
out->linkUp = Drivers::Net::E1000::IsLinkUp() ? 1 : 0;
const auto* iface = ::Net::NetIf::Active();
if (iface == nullptr) return;
out->initialized = 1;
out->linkUp = iface->IsLinkUp() ? 1 : 0;
if (iface->Type == ::Net::NetIf::Kind::Wireless) {
WifiInfo wi;
if (Drivers::Net::Wifi::GetInfo(&wi) == 0) {
out->rxPackets = wi.rxPackets;
out->txPackets = wi.txPackets;
}
CopyNetStatusDriver(out->driver, "Intel Wi-Fi (wlan0)");
} else if (Drivers::Net::E1000::IsInitialized()) {
out->rxPackets = Drivers::Net::E1000::GetRxPacketCount();
out->txPackets = Drivers::Net::E1000::GetTxPacketCount();
CopyNetStatusDriver(out->driver, "Intel 82540EM");
} else if (Drivers::Net::E1000E::IsInitialized()) {
out->initialized = 1;
out->linkUp = Drivers::Net::E1000E::IsLinkUp() ? 1 : 0;
out->polling = Drivers::Net::E1000E::RequiresPolling() ? 1 : 0;
out->rxPackets = Drivers::Net::E1000E::GetRxPacketCount();
out->txPackets = Drivers::Net::E1000E::GetTxPacketCount();
+26 -1
View File
@@ -671,6 +671,26 @@ namespace montauk::abi {
uint16_t beaconInterval; // TU
};
// Association progress reported in WifiInfo.connState.
static constexpr uint32_t WIFI_CONN_IDLE = 0;
static constexpr uint32_t WIFI_CONN_CONTEXTS_UP = 1;
static constexpr uint32_t WIFI_CONN_AUTHENTICATING = 2;
static constexpr uint32_t WIFI_CONN_AUTHENTICATED = 3;
static constexpr uint32_t WIFI_CONN_ASSOCIATING = 4;
static constexpr uint32_t WIFI_CONN_ASSOCIATED = 5;
static constexpr uint32_t WIFI_CONN_HANDSHAKING = 6;
static constexpr uint32_t WIFI_CONN_CONNECTED = 7;
static constexpr uint32_t WIFI_CONN_FAILED = 8;
// Negative results from SYS_WIFI_CONNECT.
static constexpr int WIFI_ERR_NO_ADAPTER = -1; // no adapter, or not ready
static constexpr int WIFI_ERR_NOT_FOUND = -2; // SSID absent from the scan
static constexpr int WIFI_ERR_NEED_KEY = -3; // encrypted, no passphrase
static constexpr int WIFI_ERR_UNSUPPORTED = -4; // WPA3-SAE, WEP, enterprise
static constexpr int WIFI_ERR_AUTH = -5; // key exchange rejected
static constexpr int WIFI_ERR_TIMEOUT = -6; // AP never answered
static constexpr int WIFI_ERR_FAILED = -7; // anything else
// Adapter status (returned by SYS_WIFI_INFO).
struct WifiInfo {
uint8_t mac[6];
@@ -682,7 +702,12 @@ namespace montauk::abi {
char fwVersion[32];
uint64_t rxPackets;
uint32_t fwErrors;
uint32_t connState; // 0 idle, >0 connection setup in progress
uint32_t connState; // WIFI_CONN_*
uint64_t txPackets;
char ssid[36]; // network joined, empty when disconnected
uint8_t bssid[6];
uint8_t connected; // 1 once the link can carry IP traffic
uint8_t channel;
};
struct ThermalInfo {
+149
View File
@@ -0,0 +1,149 @@
/*
* Ieee80211.hpp
* 802.11 frame and RSN constants shared by the MLME and the supplicant.
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Drivers::Net::Wifi {
// =========================================================================
// Frame control
// =========================================================================
constexpr uint16_t IEEE80211_FC0_TYPE_MASK = 0x0c;
constexpr uint16_t IEEE80211_FC0_TYPE_MGT = 0x00;
constexpr uint16_t IEEE80211_FC0_TYPE_CTL = 0x04;
constexpr uint16_t IEEE80211_FC0_TYPE_DATA = 0x08;
constexpr uint16_t IEEE80211_FC0_SUBTYPE_MASK = 0xf0;
// Management subtypes (already shifted into the frame-control byte)
constexpr uint8_t IEEE80211_SUBTYPE_ASSOC_REQ = 0x00;
constexpr uint8_t IEEE80211_SUBTYPE_ASSOC_RESP = 0x10;
constexpr uint8_t IEEE80211_SUBTYPE_REASSOC_REQ = 0x20;
constexpr uint8_t IEEE80211_SUBTYPE_PROBE_REQ = 0x40;
constexpr uint8_t IEEE80211_SUBTYPE_PROBE_RESP = 0x50;
constexpr uint8_t IEEE80211_SUBTYPE_BEACON = 0x80;
constexpr uint8_t IEEE80211_SUBTYPE_DISASSOC = 0xa0;
constexpr uint8_t IEEE80211_SUBTYPE_AUTH = 0xb0;
constexpr uint8_t IEEE80211_SUBTYPE_DEAUTH = 0xc0;
constexpr uint8_t IEEE80211_SUBTYPE_ACTION = 0xd0;
// Data subtypes
constexpr uint8_t IEEE80211_SUBTYPE_DATA = 0x00;
constexpr uint8_t IEEE80211_SUBTYPE_QOS_DATA = 0x80;
constexpr uint8_t IEEE80211_SUBTYPE_NULL = 0x40;
// Frame control byte 1
constexpr uint8_t IEEE80211_FC1_TO_DS = 0x01;
constexpr uint8_t IEEE80211_FC1_FROM_DS = 0x02;
constexpr uint8_t IEEE80211_FC1_MORE_FRAG = 0x04;
constexpr uint8_t IEEE80211_FC1_RETRY = 0x08;
constexpr uint8_t IEEE80211_FC1_PWR_MGT = 0x10;
constexpr uint8_t IEEE80211_FC1_MORE_DATA = 0x20;
constexpr uint8_t IEEE80211_FC1_PROTECTED = 0x40;
constexpr uint32_t IEEE80211_HDR_LEN = 24;
constexpr uint32_t IEEE80211_QOS_HDR_LEN = 26;
// Capability bits in beacons / association requests
constexpr uint16_t IEEE80211_CAPINFO_ESS = 0x0001;
constexpr uint16_t IEEE80211_CAPINFO_PRIVACY = 0x0010;
constexpr uint16_t IEEE80211_CAPINFO_SHORT_PREAMBLE = 0x0020;
constexpr uint16_t IEEE80211_CAPINFO_SHORT_SLOT = 0x0400;
// Authentication algorithms
constexpr uint16_t IEEE80211_AUTH_ALG_OPEN = 0;
constexpr uint16_t IEEE80211_AUTH_ALG_SAE = 3;
// Reason / status codes used here
constexpr uint16_t IEEE80211_STATUS_SUCCESS = 0;
constexpr uint16_t IEEE80211_REASON_UNSPECIFIED = 1;
constexpr uint16_t IEEE80211_REASON_DEAUTH_LEAVING = 3;
constexpr uint16_t IEEE80211_REASON_MIC_FAILURE = 14;
constexpr uint16_t IEEE80211_REASON_4WAY_TIMEOUT = 15;
// =========================================================================
// Information elements
// =========================================================================
constexpr uint8_t IEEE80211_ELEMID_SSID = 0;
constexpr uint8_t IEEE80211_ELEMID_RATES = 1;
constexpr uint8_t IEEE80211_ELEMID_DSPARMS = 3;
constexpr uint8_t IEEE80211_ELEMID_XRATES = 50;
constexpr uint8_t IEEE80211_ELEMID_RSN = 48;
constexpr uint8_t IEEE80211_ELEMID_HTCAPS = 45;
constexpr uint8_t IEEE80211_ELEMID_VENDOR = 221;
// RSN cipher / AKM suite selectors (00-0F-AC:<type>)
constexpr uint32_t RSN_OUI = 0x000fac;
constexpr uint8_t RSN_CIPHER_NONE = 0;
constexpr uint8_t RSN_CIPHER_WEP40 = 1;
constexpr uint8_t RSN_CIPHER_TKIP = 2;
constexpr uint8_t RSN_CIPHER_CCMP = 4;
constexpr uint8_t RSN_CIPHER_WEP104 = 5;
constexpr uint8_t RSN_CIPHER_BIP_CMAC = 6;
constexpr uint8_t RSN_CIPHER_GCMP = 8;
constexpr uint8_t RSN_CIPHER_GCMP_256 = 9;
constexpr uint8_t RSN_CIPHER_CCMP_256 = 10;
constexpr uint8_t RSN_AKM_8021X = 1;
constexpr uint8_t RSN_AKM_PSK = 2;
constexpr uint8_t RSN_AKM_FT_PSK = 4;
constexpr uint8_t RSN_AKM_PSK_SHA256 = 6;
constexpr uint8_t RSN_AKM_SAE = 8;
constexpr uint8_t RSN_AKM_FT_SAE = 9;
// RSN capabilities
constexpr uint16_t RSN_CAP_MFPR = 0x0040; // management protection required
constexpr uint16_t RSN_CAP_MFPC = 0x0080; // management protection capable
// =========================================================================
// LLC/SNAP: the 8-byte shim between an 802.11 data payload and an
// Ethernet II EtherType.
// =========================================================================
struct LlcSnapHeader {
uint8_t dsap; // 0xaa
uint8_t ssap; // 0xaa
uint8_t control; // 0x03
uint8_t oui[3]; // 00-00-00 (RFC 1042)
uint16_t etherType; // big endian
} __attribute__((packed));
constexpr uint32_t LLC_SNAP_LEN = 8;
constexpr uint8_t LLC_SNAP_LSAP = 0xaa;
constexpr uint16_t ETHERTYPE_IPV4 = 0x0800;
constexpr uint16_t ETHERTYPE_ARP = 0x0806;
constexpr uint16_t ETHERTYPE_EAPOL = 0x888e;
// =========================================================================
// Byte helpers (802.11 fields are little endian, EAPOL is big endian)
// =========================================================================
inline uint16_t Get16Le(const uint8_t* p) {
return (uint16_t)(p[0] | ((uint16_t)p[1] << 8));
}
inline void Put16Le(uint8_t* p, uint16_t v) {
p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8);
}
inline uint16_t Get16Be(const uint8_t* p) {
return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
}
inline void Put16Be(uint8_t* p, uint16_t v) {
p[0] = (uint8_t)(v >> 8); p[1] = (uint8_t)v;
}
inline bool AddrEqual(const uint8_t* a, const uint8_t* b) {
for (int i = 0; i < 6; i++) if (a[i] != b[i]) return false;
return true;
}
inline bool AddrIsBroadcast(const uint8_t* a) {
for (int i = 0; i < 6; i++) if (a[i] != 0xff) return false;
return true;
}
}
+105 -8
View File
@@ -17,6 +17,7 @@
#include <cstdint>
#include <Pci/Pci.hpp>
#include <CppLib/Spinlock.hpp>
#include <atomic>
#include "IwxReg.hpp"
namespace Drivers::Net::Wifi {
@@ -85,6 +86,13 @@ namespace Drivers::Net::Wifi {
// Rings
// =========================================================================
// Frames waiting in a TX queue are staged in single pages, one per
// in-flight frame. The descriptor ring itself has IWX_TX_RING_COUNT
// entries (the size the firmware was told about), but outstanding frames
// are capped at the number of staging pages so a slot can never be reused
// while the hardware is still reading it.
constexpr uint32_t IWX_TX_STAGE_SLOTS = 64;
struct IwxTxRing {
int Qid = 0;
IwxDma Desc; // IwxTfhTfd[IWX_TX_RING_COUNT]
@@ -94,6 +102,13 @@ namespace Drivers::Net::Wifi {
uint32_t Cur = 0; // ring slot (0..count-1)
uint32_t CurHw = 0; // hardware index (0..65535)
uint32_t Queued = 0;
// TX staging (data/management queues only; the command queue stages
// into its per-slot Cmd area instead).
uint8_t* Stage[IWX_TX_STAGE_SLOTS] = {};
uint64_t StagePhys[IWX_TX_STAGE_SLOTS] = {};
uint32_t StageSlots = 0;
bool Active = false; // configured with the firmware
};
struct IwxRxRing {
@@ -181,7 +196,11 @@ namespace Drivers::Net::Wifi {
IwxRxRing RxQ;
IwxTxRing CmdQ; // queue 0: host commands
IwxTxRing MgmtQ; // queue 1: management frames (connect path)
// One queue carries management frames, EAPOL and non-QoS data: the
// firmware maps non-QoS data onto the management TID anyway, and the
// driver never negotiates block-ack sessions that would need per-TID
// queues.
IwxTxRing MgmtQ;
// ALIVE / init-complete tracking (set from notification processing)
volatile bool AliveIntr = false;
@@ -194,18 +213,29 @@ namespace Drivers::Net::Wifi {
uint32_t UmacErrorTable = 0;
uint32_t LmacErrorTable = 0;
uint32_t LastCmdId = 0;
uint32_t LastCmdLen = 0;
uint8_t LastCmdPayload[192] = {}; // dumped on a firmware assert
bool LtrEnabled = false; // PCIe LTR capability advertised
// Synchronous-command bookkeeping (commands are fully serialized)
kcp::Spinlock CmdLock; // serializes SendCmd callers
// A Mutex, deliberately not a Spinlock: kcp::Spinlock disables
// interrupts, and this lock is held across a wall-clock wait for the
// firmware's response. With interrupts off the millisecond counter
// (driven by the APIC timer interrupt) never advances, so the timeout
// could never expire and one unanswered command would hang the machine
// with interrupts disabled. Every caller is process or idle context;
// the hard IRQ only latches WorkPending.
kcp::Mutex CmdLock; // serializes SendCmd callers
volatile bool CmdDone = false;
volatile bool CmdWantResp = false;
uint8_t CmdRespBuf[4096];
volatile uint32_t CmdRespLen = 0;
uint32_t CmdIdx = 0; // ring slot of in-flight command
// Reentrancy guard for ProcessEvents
volatile bool InProcessEvents = false;
// Reentrancy guard for ProcessEvents. Interrupts stay enabled while
// commands wait, so two cores can genuinely race here; a plain bool
// would let both through.
std::atomic_flag InProcessEvents = ATOMIC_FLAG_INIT;
IwxNvmData Nvm;
IwxChannel Channels[IWX_MAX_CHANNELS_TRACKED];
@@ -215,8 +245,19 @@ namespace Drivers::Net::Wifi {
volatile bool ScanActive = false;
volatile bool ScanCompleted = false;
// Band of the BSS currently being joined; picks the legacy rate used
// for management frames.
bool Is5GHz = false;
// Serializes frame TX (the netif and the MLME both queue frames).
// Process/idle context only, so it keeps interrupts enabled too.
kcp::Mutex TxLock;
// Statistics/diagnostics
uint64_t RxPackets = 0;
uint64_t TxPackets = 0;
uint64_t TxFailures = 0;
uint64_t RxDataPackets = 0;
uint64_t FwErrors = 0;
};
@@ -271,8 +312,28 @@ namespace Drivers::Net::Wifi {
bool IwxCheckRfKill();
// TX queue management (used by connect path)
bool IwxEnableTxq(int staId, int qid, int tid);
// TX queue management (used by the connect path). On the new data-path
// API the firmware picks the queue number and reports it back, so `qid` is
// only a hint; the assigned value lands in ring.Qid.
bool IwxEnableTxq(IwxTxRing& ring, int staId, int qid, int tid);
void IwxDisableTxq(IwxTxRing& ring, int staId, int tid);
// Transmit one 802.11 frame. The header and payload are copied into a
// staging page, wrapped in a TX_CMD and handed to the queue.
// encrypt - let the firmware apply the installed key (data frames
// after the handshake); cleared for auth/assoc/EAPOL
// fixedRate - send at the lowest basic rate instead of letting the
// firmware's rate control pick (required for management
// frames, which have no rate table yet)
bool IwxTxFrame(IwxTxRing& ring, const uint8_t* hdr, uint32_t hdrLen,
const uint8_t* payload, uint32_t payloadLen,
bool encrypt, bool fixedRate);
// Install or remove a hardware key for the connected station.
bool IwxSetKey(const uint8_t* key, uint32_t keyLen, uint8_t keyIdx,
bool pairwise, uint8_t cipher, const uint8_t* rsc);
bool IwxRemoveKey(uint8_t keyIdx, bool pairwise, uint8_t cipher,
uint32_t keyLen);
// =========================================================================
// Firmware file parsing (IwxFw.cpp)
@@ -300,15 +361,51 @@ namespace Drivers::Net::Wifi {
void WifiRxMgmtFrame(const uint8_t* frame, uint32_t len, uint8_t channel,
int8_t rssiDbm);
// TX completion, called from RX processing for a TX_CMD response.
void IwxTxComplete(int qid, int idx, uint32_t status);
// =========================================================================
// Connect groundwork (IwxConnect.cpp) - UNTESTED scaffolding
// Connect / MLME (IwxConnect.cpp)
// =========================================================================
// Association states reported through WifiInfo.connState.
enum class IwxConnStateId : int {
Idle = 0,
ContextsUp,
Authenticating,
Authenticated,
Associating,
Associated, // 802.11 link up; open networks stop here
Handshaking, // WPA 4-way in progress
Connected, // keys installed (or open); data can flow
Failed,
};
// `rsnIe` is the AP's RSN element body (may be null for open networks).
bool IwxConnectStart(const uint8_t* bssid, uint8_t channel, bool is5GHz,
const char* ssid);
const char* ssid, const char* password,
const uint8_t* rsnIe, uint32_t rsnIeLen,
uint16_t beaconInterval, uint8_t dtimPeriod);
void IwxConnectAbort();
// Why the last IwxConnectStart() refused: false means the radio or the
// firmware failed, true means the network's security is unsupported. The
// two need very different advice, so they must not be conflated.
bool IwxConnectRefusedForSecurity();
void IwxConnectRxMgmt(const uint8_t* frame, uint32_t len);
// Inbound 802.11 data frame from the RX path.
void IwxConnectRxData(const uint8_t* frame, uint32_t len);
// Apply state changes the RX path queued (it cannot send commands itself).
void IwxConnectService();
int IwxConnectState();
// Link status for the network stack.
bool IwxLinkUp(); // associated and keyed
const uint8_t* IwxConnectBssid();
const char* IwxConnectSsid();
// Send an Ethernet frame over the air (called by the netif).
bool IwxConnectSendEthernet(const uint8_t* frame, uint32_t len);
// Sink for decapsulated Ethernet frames, implemented by Wifi.cpp.
void WifiRxEthernet(const uint8_t* frame, uint32_t len);
}
File diff suppressed because it is too large Load Diff
+37 -5
View File
@@ -7,6 +7,7 @@
*/
#include "Iwx.hpp"
#include "Ieee80211.hpp"
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -629,12 +630,23 @@ namespace Drivers::Net::Wifi {
uint32_t offset = (uint32_t)(frame - (const uint8_t*)pkt);
if (offset + len > bufLen) return;
// The firmware pads the header to a 4-byte boundary when the flag is
// set; the payload then starts two bytes later.
// The firmware pads a MAC header that is not a multiple of four bytes
// (in practice: every QoS data header) out to alignment by inserting
// two bytes between the header -- and the crypto IV, if any -- and
// the payload; mpdu_len counts them. Cut them out so the rest of the
// driver sees a contiguous 802.11 frame. Getting this wrong loses
// every QoS data frame while management frames keep working.
if (desc->mac_flags2 & IWX_RX_MPDU_MFLG2_PAD) {
if (len < 2) return;
frame += 2;
if (offset + 2 + len > bufLen) return;
uint32_t hdrLen = 24;
if ((frame[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA
&& (frame[0] & IEEE80211_SUBTYPE_QOS_DATA))
hdrLen += 2;
if (frame[1] & IEEE80211_FC1_PROTECTED)
hdrLen += IWX_CCMP_HDR_LEN;
if (len < hdrLen + 2) return;
memmove((uint8_t*)frame + hdrLen, frame + hdrLen + 2,
len - hdrLen - 2);
len -= 2;
}
int energyA = desc->v3.energy_a ? -(int)desc->v3.energy_a : -256;
@@ -642,6 +654,26 @@ namespace Drivers::Net::Wifi {
int rssi = energyA > energyB ? energyA : energyB;
if (rssi < -128) rssi = -128;
uint8_t type = (uint8_t)(frame[0] & IEEE80211_FC0_TYPE_MASK);
if (type == IEEE80211_FC0_TYPE_DATA) {
// Encrypted frames are decrypted by the firmware, which strips the
// MIC but leaves the CCMP/GCMP header in place; drop anything that
// failed to decrypt rather than handing up ciphertext.
if (frame[1] & IEEE80211_FC1_PROTECTED) {
uint32_t sec = desc->status & IWX_RX_MPDU_STATUS_SEC_MASK;
bool ok = (desc->status & IWX_RX_MPDU_STATUS_DECRYPTED)
&& (desc->status & IWX_RX_MPDU_STATUS_MIC_OK)
&& (sec == IWX_RX_MPDU_STATUS_SEC_CCM
|| sec == IWX_RX_MPDU_STATUS_SEC_GCM);
if (!ok) return;
}
IwxConnectRxData(frame, len);
return;
}
if (type != IEEE80211_FC0_TYPE_MGT) return;
WifiRxMgmtFrame(frame, len, desc->v3.channel, (int8_t)rssi);
}
+377 -7
View File
@@ -567,7 +567,10 @@ inline uint32_t IwxRxPacketPayloadLen(const IwxRxPacket* pkt) {
return IwxRxPacketLen(pkt) - sizeof(IwxCmdHeader);
}
// DQA queue assignment
// DQA queue assignment. The management queue number is only a hint: on the v3
// data-path API the firmware assigns the queue and reports the number back.
// TID 15 carries management frames, EAPOL and non-QoS data -- the firmware maps
// non-QoS data onto this TID regardless.
constexpr int IWX_DQA_CMD_QUEUE = 0;
constexpr int IWX_DQA_MGMT_QUEUE = 1;
constexpr int IWX_MGMT_TID = 15;
@@ -949,6 +952,20 @@ constexpr uint8_t IWX_SCAN_ADWELL_N_APS_SOCIAL_CHS = 2;
constexpr uint32_t IWX_RX_MPDU_RES_STATUS_CRC_OK = 1u << 0;
constexpr uint32_t IWX_RX_MPDU_RES_STATUS_OVERRUN_OK = 1u << 1;
constexpr uint32_t IWX_RX_MPDU_STATUS_KEY_VALID = 1u << 3;
constexpr uint32_t IWX_RX_MPDU_STATUS_ICV_OK = 1u << 5;
constexpr uint32_t IWX_RX_MPDU_STATUS_MIC_OK = 1u << 6;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_MASK = 0x7u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_NONE = 0x0u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_WEP = 0x1u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_CCM = 0x2u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_TKIP = 0x3u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_EXT_ENC = 0x4u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_GCM = 0x5u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_DECRYPTED = 1u << 11;
// The hardware strips the CCMP/GCMP MIC but leaves the 8-byte IV in place.
constexpr uint32_t IWX_CCMP_HDR_LEN = 8;
constexpr uint8_t IWX_RX_MPDU_MFLG2_PAD = 0x20;
constexpr uint8_t IWX_RX_MPDU_MFLG2_AMSDU = 0x40;
@@ -1000,21 +1017,46 @@ constexpr uint32_t IWX_FW_CTXT_ACTION_REMOVE = 3;
constexpr uint32_t IWX_LMAC_24G_INDEX = 0;
constexpr uint32_t IWX_LMAC_5G_INDEX = 1;
// PHY context
struct IwxFwChannelInfoV1 {
// PHY context.
//
// The channel-info sub-structure has two shapes and the firmware picks which
// one it expects via IWX_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS. AX211 firmware 89
// sets that bit, so it wants the v2 form: a 32-bit channel number first, then
// the band. Sending the 4-byte v1 form to a firmware expecting 8 bytes
// shifts lmac_id/rxchain_info and asserts the firmware, which then stops
// answering host commands entirely.
struct IwxFwChannelInfoV1 { // CHANNEL_CONFIG_API_S_VER_1
uint8_t band;
uint8_t channel;
uint8_t width;
uint8_t ctrl_pos;
} __attribute__((packed));
struct IwxFwChannelInfo { // CHANNEL_CONFIG_API_S_VER_2
uint32_t channel;
uint8_t band;
uint8_t width;
uint8_t ctrl_pos;
uint8_t reserved;
} __attribute__((packed));
constexpr uint8_t IWX_PHY_VHT_CHANNEL_MODE20 = 0x0;
constexpr uint8_t IWX_PHY_VHT_CTRL_POS_1_BELOW = 0x0;
constexpr uint32_t IWX_PHY_RX_CHAIN_VALID_POS = 1;
constexpr uint32_t IWX_PHY_RX_CHAIN_CNT_POS = 10;
constexpr uint32_t IWX_PHY_RX_CHAIN_MIMO_CNT_POS = 12;
struct IwxPhyContextCmd { // PHY_CONTEXT_CMD_API_VER_3/4 (non-UHB)
struct IwxPhyContextCmd { // PHY_CONTEXT_CMD_API_S_VER_3/4, UHB
uint32_t id_and_color;
uint32_t action;
IwxFwChannelInfo ci;
uint32_t lmac_id;
uint32_t rxchain_info; // reserved from VER_4 on
uint32_t dsp_cfg_flags;
uint32_t reserved;
} __attribute__((packed));
struct IwxPhyContextCmdV1Chan { // same command, pre-UHB channel info
uint32_t id_and_color;
uint32_t action;
IwxFwChannelInfoV1 ci;
@@ -1024,11 +1066,15 @@ struct IwxPhyContextCmd { // PHY_CONTEXT_CMD_API_VER_3/4 (non-UHB)
uint32_t reserved;
} __attribute__((packed));
static_assert(sizeof(IwxPhyContextCmd) == 32, "PHY context (UHB) must be 32 bytes");
static_assert(sizeof(IwxPhyContextCmdV1Chan) == 28, "PHY context (legacy) must be 28 bytes");
// MAC context
constexpr uint32_t IWX_FW_MAC_TYPE_BSS_STA = 5;
constexpr uint32_t IWX_TSF_ID_A = 0;
constexpr uint32_t IWX_AC_NUM = 4;
constexpr uint32_t IWX_MAC_QOS_FLG_UPDATE_EDCA = 1u << 0;
constexpr uint32_t IWX_MAC_QOS_FLG_TGN = 1u << 1;
constexpr uint32_t IWX_MAC_FILTER_IN_CONTROL_AND_MGMT = 1u << 1;
constexpr uint32_t IWX_MAC_FILTER_ACCEPT_GRP = 1u << 2;
constexpr uint32_t IWX_MAC_FILTER_IN_BEACON = 1u << 6;
@@ -1055,6 +1101,16 @@ struct IwxMacDataSta {
uint32_t assoc_beacon_arrive_time;
} __attribute__((packed));
// The per-mac-type tail of MAC_CONTEXT_CMD is a union in the firmware API, so
// the command length is that of its LARGEST member -- p2p_sta, which is
// iwl_mac_data_sta plus a ctwin word. Sending only the sta member makes the
// command four bytes short and asserts the firmware (observed on AX211 fw 89:
// UMAC error 0x201002FF on command 0x128).
struct IwxMacDataP2pSta {
IwxMacDataSta sta;
uint32_t ctwin;
} __attribute__((packed));
struct IwxMacCtxCmd { // IWX_MAC_CONTEXT_CMD_API_S_VER_1 (sta)
uint32_t id_and_color;
uint32_t action;
@@ -1072,9 +1128,15 @@ struct IwxMacCtxCmd { // IWX_MAC_CONTEXT_CMD_API_S_VER_1 (sta)
uint32_t filter_flags;
uint32_t qos_flags;
IwxAcQos ac[IWX_AC_NUM + 1];
IwxMacDataSta sta;
union {
IwxMacDataSta sta;
IwxMacDataP2pSta p2p_sta; // the largest member: sizes the command
} u;
} __attribute__((packed));
static_assert(sizeof(IwxMacCtxCmd) == 148,
"MAC context command must stay 148 bytes (union sized by p2p_sta)");
// Binding context
constexpr uint32_t IWX_MAX_MACS_IN_BINDING = 3;
struct IwxBindingCmd {
@@ -1178,8 +1240,9 @@ struct IwxRlcConfigCmd {
uint8_t reserved[3];
} __attribute__((packed));
// Session protection
constexpr uint32_t IWX_SESSION_PROTECT_CONF_ASSOC = 1;
// Session protection. ASSOC is the first value of
// enum iwl_session_prot_conf_id, i.e. zero -- 1 is GO_CLIENT_ASSOC.
constexpr uint32_t IWX_SESSION_PROTECT_CONF_ASSOC = 0;
struct IwxSessionProtCmd {
uint32_t id_and_color;
uint32_t action;
@@ -1196,6 +1259,313 @@ struct IwxSessionProtNotif {
uint32_t conf_id;
} __attribute__((packed));
// =============================================================================
// MLD API (MAC_CONF group)
//
// Firmware that advertises IWX_UCODE_TLV_CAPA_MLD_API_SUPPORT -- which AX211
// firmware 89 does -- implements these instead of the legacy MAC_CONTEXT_CMD /
// BINDING_CONTEXT_CMD / ADD_STA. The legacy commands are simply absent, and
// sending one asserts the firmware.
//
// Every layout and size below was confirmed against a host-command trace taken
// from Linux driving this same adapter and firmware (see
// tests/wifi/decode_iwl_trace.py), not inferred from a kernel header.
// =============================================================================
constexpr uint8_t IWX_MAC_CONFIG_CMD = 0x08; // MAC_CONF group
constexpr uint8_t IWX_LINK_CONFIG_CMD = 0x09;
constexpr uint8_t IWX_STA_CONFIG_CMD = 0x0a;
constexpr uint8_t IWX_AUX_STA_CMD = 0x0b;
constexpr uint8_t IWX_STA_REMOVE_CMD = 0x0c;
// iwl_mac_config_filter_flags
constexpr uint32_t IWX_MAC_CFG_FILTER_PROMISC = 1u << 0;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_CTRL_MGMT = 1u << 1;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_GRP = 1u << 2;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_BEACON = 1u << 3;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_BCAST_PROBE_RESP = 1u << 4;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_PROBE_REQ = 1u << 5;
struct IwxMacClientData { // MAC_CONTEXT_CONFIG_CLIENT_DATA_API_S_VER_2
uint8_t is_assoc;
uint8_t esr_transition_timeout;
uint16_t medium_sync_delay;
uint16_t assoc_id;
uint16_t reserved1;
uint16_t data_policy;
uint16_t reserved2;
uint32_t ctwin;
} __attribute__((packed));
struct IwxMacConfigCmd { // MAC_CONTEXT_CONFIG_CMD_API_S_VER_2
uint32_t id_and_color;
uint32_t action;
uint32_t mac_type;
uint8_t local_mld_addr[6];
uint16_t reserved_for_local_mld_addr;
uint32_t filter_flags;
uint16_t he_support;
uint16_t he_ap_support;
uint32_t eht_support;
uint32_t nic_not_ack_enabled;
IwxMacClientData client; // union with p2p_dev; client is the largest
} __attribute__((packed));
static_assert(sizeof(IwxMacConfigCmd) == 52, "MAC_CONFIG_CMD must be 52 bytes");
// iwl_link_ctx_modify_flags
constexpr uint32_t IWX_LINK_MODIFY_ACTIVE = 1u << 0;
constexpr uint32_t IWX_LINK_MODIFY_RATES_INFO = 1u << 1;
constexpr uint32_t IWX_LINK_MODIFY_PROTECT_FLAGS = 1u << 2;
constexpr uint32_t IWX_LINK_MODIFY_QOS_PARAMS = 1u << 3;
constexpr uint32_t IWX_LINK_MODIFY_BEACON_TIMING = 1u << 4;
constexpr uint32_t IWX_LINK_MODIFY_HE_PARAMS = 1u << 5;
constexpr uint32_t IWX_LINK_MODIFY_ALL = 0xff;
struct IwxHeBackoffConf { // AC_QOS_MU_EDCA_API_S
uint16_t cwmin;
uint16_t cwmax;
uint16_t aifsn;
uint16_t mu_time;
} __attribute__((packed));
struct IwxLinkConfigCmd { // LINK_CONTEXT_CONFIG_CMD_API_S_VER_1/2/3
uint32_t action;
uint32_t link_id;
uint32_t mac_id;
uint32_t phy_id; // IWX_FW_CTXT_INVALID until bound
uint8_t local_link_addr[6];
uint16_t reserved_for_local_link_addr;
uint32_t modify_mask;
uint32_t active;
uint32_t listen_lmac;
uint32_t cck_rates;
uint32_t ofdm_rates;
uint32_t cck_short_preamble;
uint32_t short_slot;
uint32_t protection_flags;
uint32_t qos_flags;
IwxAcQos ac[IWX_AC_NUM + 1];
uint8_t htc_trig_based_pkt_ext;
uint8_t rand_alloc_ecwmin;
uint8_t rand_alloc_ecwmax;
uint8_t ndp_fdbk_buff_th_exp;
IwxHeBackoffConf trig_based_txf[IWX_AC_NUM];
uint32_t bi;
uint32_t dtim_interval;
uint16_t puncture_mask; // removed in _VER_3
uint16_t frame_time_rts_th;
uint32_t flags;
uint32_t flags_mask;
uint8_t ref_bssid_addr[6];
uint16_t reserved_for_ref_bssid_addr;
uint8_t bssid_index;
uint8_t bss_color;
uint8_t spec_link_id;
uint8_t reserved2;
uint8_t ibss_bssid_addr[6];
uint16_t reserved_for_ibss_bssid_addr;
uint32_t reserved3[8];
} __attribute__((packed));
static_assert(sizeof(IwxLinkConfigCmd) == 208, "LINK_CONFIG_CMD must be 208 bytes");
// 2 spatial streams x 5 bandwidth indices x 2 thresholds
struct IwxHePktExtV2 {
uint8_t pkt_ext_qam_th[2][5][2];
} __attribute__((packed));
struct IwxStaConfigCmd { // STA_CMD_API_S_VER_1
uint32_t sta_id;
uint32_t link_id;
uint8_t peer_mld_address[6];
uint16_t reserved_for_peer_mld_address;
uint8_t peer_link_address[6];
uint16_t reserved_for_peer_link_address;
uint32_t station_type;
uint32_t assoc_id;
uint32_t beamform_flags;
uint32_t mfp;
uint32_t mimo;
uint32_t mimo_protection;
uint32_t ack_enabled;
uint32_t trig_rnd_alloc;
uint32_t tx_ampdu_spacing;
uint32_t tx_ampdu_max_size;
uint32_t sp_length;
uint32_t uapsd_acs;
IwxHePktExtV2 pkt_ext;
uint32_t htc_flags;
} __attribute__((packed));
static_assert(sizeof(IwxStaConfigCmd) == 96, "STA_CONFIG_CMD must be 96 bytes");
struct IwxStaRemoveCmd {
uint32_t sta_id;
} __attribute__((packed));
// =============================================================================
// TX path (AX210 / "new TX API")
// =============================================================================
// TX_CMD on a data queue carries the *short* 4-byte command header and stays in
// group 0; only host commands on the command queue are re-tagged into
// LONG_GROUP. Layout of a queued frame:
//
// [IwxCmdHeader 4][IwxTxCmdGen3 28][802.11 header][pad to 4][payload]
//
// TB0 covers the first 20 bytes, TB1 the rest of the header block (dword
// aligned) and TB2 the payload, matching iwl_txq_gen2_build_tx().
struct IwxDramSecInfo {
uint32_t pn_low;
uint16_t pn_high;
uint16_t aux_info;
} __attribute__((packed)); // DRAM_SEC_INFO_API_S_VER_1
struct IwxTxCmdGen3 {
uint16_t len; // total 802.11 frame length, plaintext
uint16_t flags; // IWX_TX_FLAGS_*
uint32_t offload_assist;
IwxDramSecInfo dram_info;
uint32_t rate_n_flags;
uint8_t reserved[8]; // named "ttl" in TX_CMD_API_S_VER_8
// 802.11 header follows
} __attribute__((packed)); // TX_CMD_API_S_VER_8 / _10
static_assert(sizeof(IwxTxCmdGen3) == 28, "TX command header must stay 28 bytes");
// iwl_tx_cmd_flags (TX_FLAGS_BITS_API_S_VER_3)
constexpr uint16_t IWX_TX_FLAGS_CMD_RATE = 1 << 0; // use rate_n_flags
constexpr uint16_t IWX_TX_FLAGS_ENCRYPT_DIS = 1 << 1; // send in the clear
constexpr uint16_t IWX_TX_FLAGS_HIGH_PRI = 1 << 2;
constexpr uint16_t IWX_TX_FLAGS_RTS = 1 << 3;
constexpr uint16_t IWX_TX_FLAGS_CTS = 1 << 4;
// iwl_tx_offload_assist_flags_pos
constexpr uint32_t IWX_TX_CMD_OFFLD_MH_SIZE_POS = 8; // header length in words
constexpr uint32_t IWX_TX_CMD_OFFLD_MH_MASK = 0x1f;
constexpr uint32_t IWX_TX_CMD_OFFLD_PAD = 1u << 13;
constexpr uint32_t IWX_TX_CMD_OFFLD_AMSDU = 1u << 14;
// rate_n_flags. The firmware advertises which encoding it wants: TX_CMD
// notification version 7 and later use the "version 2" layout, where bits 10-8
// select the modulation and bits 3-0 index the legacy rate table. Older
// firmware uses the version 1 layout, which carries the PLCP value directly and
// flags CCK with bit 9. Antenna selection sits at bits 15-14 in both.
constexpr uint32_t IWX_RATE_MCS_ANT_POS = 14;
constexpr uint32_t IWX_RATE_MCS_ANT_A = 1u << IWX_RATE_MCS_ANT_POS;
constexpr uint32_t IWX_RATE_MCS_ANT_B = 2u << IWX_RATE_MCS_ANT_POS;
// version 2
constexpr uint32_t IWX_RATE_MCS_MOD_TYPE_POS = 8;
constexpr uint32_t IWX_RATE_MCS_MOD_CCK = 0u << IWX_RATE_MCS_MOD_TYPE_POS;
constexpr uint32_t IWX_RATE_MCS_MOD_LEGACY_OFDM = 1u << IWX_RATE_MCS_MOD_TYPE_POS;
constexpr uint32_t IWX_RATE_LEGACY_RATE_MSK = 0x7;
constexpr uint32_t IWX_RATE_MCS_CHAN_WIDTH_20 = 0u << 11;
// version 1 (PLCP encoded directly in bits 7-0)
constexpr uint32_t IWX_RATE_MCS_CCK_MSK_V1 = 1u << 9;
constexpr uint8_t IWX_RATE_1M_PLCP = 10;
constexpr uint8_t IWX_RATE_6M_PLCP = 13;
// TX response (TX_CMD notification). Only the leading fields are used: the
// driver just needs the slot back and a success/failure verdict.
constexpr uint32_t IWX_TX_STATUS_MSK = 0x000000ff;
constexpr uint32_t IWX_TX_STATUS_SUCCESS = 0x01;
constexpr uint32_t IWX_TX_STATUS_DIRECT_DONE = 0x02;
struct IwxTxResp {
uint8_t frame_count;
uint8_t bt_kill_count;
uint8_t failure_rts;
uint8_t failure_frame;
uint32_t initial_rate;
uint16_t wireless_media_time;
uint8_t pa_status;
uint8_t pa_integ_res_a[3];
uint8_t pa_integ_res_b[3];
uint8_t pa_integ_res_c[3];
uint16_t measurement_req_id;
uint8_t reduced_tpc;
uint8_t reserved;
uint32_t tfd_info;
uint16_t seq_ctl;
uint16_t byte_cnt;
uint8_t tlc_info;
uint8_t ra_tid;
uint16_t frame_ctrl;
// followed by per-frame status entries; entry 0 is what matters here
uint16_t status;
uint16_t sequence;
} __attribute__((packed));
// =============================================================================
// ADD_STA_KEY (hardware key installation)
// =============================================================================
// iwl_sta_key_flag
constexpr uint16_t IWX_STA_KEY_FLG_NO_ENC = 0 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_WEP = 1 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_CCM = 2 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_TKIP = 3 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_EXT = 4 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_GCMP = 5 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_CMAC = 6 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_EN_MSK = 7 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_WEP_KEY_MAP = 1 << 3;
constexpr uint16_t IWX_STA_KEY_FLG_KEYID_POS = 8;
constexpr uint16_t IWX_STA_KEY_FLG_KEYID_MSK = 3 << IWX_STA_KEY_FLG_KEYID_POS;
constexpr uint16_t IWX_STA_KEY_NOT_VALID = 1 << 11;
constexpr uint16_t IWX_STA_KEY_FLG_KEY_32BYTES = 1 << 12;
constexpr uint16_t IWX_STA_KEY_MULTICAST = 1 << 14;
constexpr uint16_t IWX_STA_KEY_MFP = 1 << 15;
struct IwxAddStaKeyCommon {
uint8_t sta_id;
uint8_t key_offset;
uint16_t key_flags;
uint8_t key[32];
uint8_t rx_secur_seq_cnt[16];
} __attribute__((packed));
struct IwxAddStaKeyCmd {
IwxAddStaKeyCommon common;
uint64_t rx_mic_key;
uint64_t tx_mic_key;
uint64_t transmit_seq_cnt;
} __attribute__((packed));
static_assert(sizeof(IwxAddStaKeyCmd) == 76, "ADD_STA_KEY layout changed");
// SEC_KEY_CMD (DATA_PATH group): key installation for the MLD firmware. The
// legacy ADD_STA_KEY above is absent from fw 89 like the rest of the legacy
// station API; the Linux trace installs both the PTK and the GTK with this.
constexpr uint8_t IWX_SEC_KEY_CMD = 0x18;
// iwl_sec_key_flags: cipher in the low 3 bits, modifiers above.
constexpr uint32_t IWX_SEC_KEY_FLAG_CIPHER_CCMP = 0x02;
constexpr uint32_t IWX_SEC_KEY_FLAG_CIPHER_TKIP = 0x03;
constexpr uint32_t IWX_SEC_KEY_FLAG_CIPHER_GCMP = 0x05;
constexpr uint32_t IWX_SEC_KEY_FLAG_NO_TX = 0x08;
constexpr uint32_t IWX_SEC_KEY_FLAG_KEY_SIZE = 0x10; // 256-bit key
constexpr uint32_t IWX_SEC_KEY_FLAG_MFP = 0x20;
constexpr uint32_t IWX_SEC_KEY_FLAG_MCAST_KEY = 0x40;
struct IwxSecKeyCmd { // SEC_KEY_CMD_API_S_VER_1 (the add form)
uint32_t action; // IWX_FW_CTXT_ACTION_*
uint32_t sta_mask;
uint32_t key_id;
uint32_t key_flags;
uint8_t key[32];
uint8_t tkip_mic_rx_key[8];
uint8_t tkip_mic_tx_key[8];
uint64_t rx_seq;
uint64_t tx_seq;
} __attribute__((packed));
static_assert(sizeof(IwxSecKeyCmd) == 80, "SEC_KEY_CMD must be 80 bytes");
// =============================================================================
// Firmware error log (read from device SRAM after an assert)
// =============================================================================
+367 -12
View File
@@ -17,6 +17,7 @@
*/
#include "Iwx.hpp"
#include "Ieee80211.hpp"
#include <Pci/Pci.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
@@ -459,11 +460,15 @@ namespace Drivers::Net::Wifi {
asm volatile("" ::: "memory");
}
static bool IwxAllocTxRing(IwxTxRing& ring, int qid) {
// `stageSlots` reserves one page per concurrently queued frame; pass 0 for
// queues that only ever carry host commands.
static bool IwxAllocTxRing(IwxTxRing& ring, int qid, uint32_t stageSlots = 0) {
ring.Qid = qid;
ring.Cur = 0;
ring.CurHw = 0;
ring.Queued = 0;
ring.StageSlots = 0;
ring.Active = false;
if (!IwxDmaAlloc(ring.Desc, sizeof(IwxTfhTfd) * IWX_TX_RING_COUNT))
return false;
@@ -476,6 +481,15 @@ namespace Drivers::Net::Wifi {
// are staged in this page instead of the per-slot command area.
if (!IwxDmaAlloc(ring.Bounce, 4096))
return false;
if (stageSlots > IWX_TX_STAGE_SLOTS) stageSlots = IWX_TX_STAGE_SLOTS;
for (uint32_t i = 0; i < stageSlots; i++) {
void* p = Memory::g_pfa->AllocateZeroed();
if (!p) return false;
ring.Stage[i] = (uint8_t*)p;
ring.StagePhys[i] = Memory::SubHHDM(p);
ring.StageSlots = i + 1;
}
return true;
}
@@ -484,6 +498,14 @@ namespace Drivers::Net::Wifi {
IwxDmaFree(ring.BcTbl);
IwxDmaFree(ring.Cmd);
IwxDmaFree(ring.Bounce);
for (uint32_t i = 0; i < ring.StageSlots; i++) {
if (ring.Stage[i]) {
Memory::g_pfa->Free(ring.Stage[i]);
ring.Stage[i] = nullptr;
}
}
ring.StageSlots = 0;
ring.Active = false;
}
static void IwxResetTxRing(IwxTxRing& ring) {
@@ -846,6 +868,9 @@ namespace Drivers::Net::Wifi {
return -1;
}
void IwxDumpFwError();
static uint32_t g_cmdTimeouts = 0; // consecutive unanswered commands
bool IwxSendCmd(IwxHostCmd& hcmd) {
if (g_iwx.State == IwxFwState::Error) return false;
@@ -909,6 +934,12 @@ namespace Drivers::Net::Wifi {
g_iwx.CmdDone = false;
g_iwx.LastCmdId = code;
// Keep the payload so a firmware assert can show exactly what it
// choked on -- struct mismatches are invisible without the bytes.
g_iwx.LastCmdLen = hcmd.Len;
uint32_t keep = hcmd.Len < sizeof(g_iwx.LastCmdPayload)
? hcmd.Len : (uint32_t)sizeof(g_iwx.LastCmdPayload);
if (hcmd.Data && keep) memcpy(g_iwx.LastCmdPayload, hcmd.Data, keep);
g_iwx.CmdWantResp = hcmd.WantResp;
g_iwx.CmdRespLen = 0;
g_iwx.CmdIdx = idx;
@@ -921,18 +952,47 @@ namespace Drivers::Net::Wifi {
// Wait for the firmware's response/ack. Commands are serialized by
// CmdLock, so exactly one can be in flight and the completion is
// unambiguous.
// Two independent bounds. The wall clock is the intended one, but it
// is driven by the timer interrupt, so anything that leaves this loop
// running with interrupts disabled would spin forever and take the
// whole machine down with it -- the spin cap makes that impossible.
// Bail immediately if the firmware has asserted, because it will never
// answer this or any later command.
constexpr uint32_t MAX_SPINS = 20000; // ~2 s at 100 us
bool ok = false;
bool died = false;
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < 1000) {
for (uint32_t spins = 0; spins < MAX_SPINS; spins++) {
IwxProcessEvents();
if (g_iwx.CmdDone) { ok = true; break; }
if (g_iwx.State == IwxFwState::Error) { died = true; break; }
if (Timekeeping::GetMilliseconds() - start >= 1000) break;
IwxDelayUs(100);
}
if (!ok) {
KernelLogStream(WARNING, "WiFi") << "Command 0x" << base::hex
<< (uint64_t)code << base::dec << " timed out";
<< (uint64_t)code << base::dec
<< (died ? " abandoned: firmware has stopped responding"
: " timed out");
if (ring.Queued > 0) ring.Queued--;
if (!died) {
// Dump on the first silence: the firmware's error table names
// the command that asserted, and it is overwritten as later
// commands go unanswered.
if (++g_cmdTimeouts == 1) IwxDumpFwError();
// Repeated silence means it is wedged and every later command
// would burn the same timeout, so stop trying. A single late
// response is not worth disabling the adapter over.
if (g_cmdTimeouts >= 3) {
KernelLogStream(ERROR, "WiFi")
<< "Firmware stopped responding to host commands";
g_iwx.FwErrors++;
g_iwx.State = IwxFwState::Error;
}
}
} else {
g_cmdTimeouts = 0;
}
g_iwx.CmdWantResp = false;
@@ -969,10 +1029,10 @@ namespace Drivers::Net::Wifi {
// TX queue configuration (used by the connect path)
// =========================================================================
bool IwxEnableTxq(int staId, int qid, int tid) {
IwxTxRing& ring = g_iwx.MgmtQ;
bool IwxEnableTxq(IwxTxRing& ring, int staId, int qid, int tid) {
IwxResetTxRing(ring);
ring.Qid = qid;
ring.Active = false;
int cmdVer = IwxLookupCmdVer(IWX_DATA_PATH_GROUP, IWX_SCD_QUEUE_CONFIG_CMD);
@@ -1016,14 +1076,280 @@ namespace Drivers::Net::Wifi {
return false;
auto* pkt = (IwxRxPacket*)g_iwx.CmdRespBuf;
auto* resp = (IwxTxQueueCfgRsp*)pkt->data;
// On the v3 data-path API the firmware owns queue assignment: `qid` is
// only a hint and the response names the queue we actually got.
if (resp->queue_number != qid) {
KernelLogStream(WARNING, "WiFi") << "Firmware assigned queue "
<< (uint64_t)resp->queue_number << ", expected " << (uint64_t)qid;
KernelLogStream(INFO, "WiFi") << "Firmware assigned TX queue "
<< (uint64_t)resp->queue_number << " (asked for "
<< (uint64_t)qid << ")";
}
ring.Qid = resp->queue_number;
ring.Active = true;
return true;
}
void IwxDisableTxq(IwxTxRing& ring, int staId, int tid) {
if (!ring.Active) return;
ring.Active = false;
int cmdVer = IwxLookupCmdVer(IWX_DATA_PATH_GROUP, IWX_SCD_QUEUE_CONFIG_CMD);
if (cmdVer == 3) {
IwxScdQueueCfgCmd cmd = {};
cmd.operation = IWX_SCD_QUEUE_REMOVE;
cmd.u.remove.sta_mask = 1u << staId;
cmd.u.remove.tid = (uint32_t)tid;
IwxSendCmdPdu(IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_SCD_QUEUE_CONFIG_CMD),
&cmd, sizeof(cmd));
} else {
IwxTxQueueCfgCmd cmd = {};
cmd.sta_id = (uint8_t)staId;
cmd.tid = (uint8_t)tid;
cmd.flags = 0; // clear ENABLE_QUEUE
cmd.cb_size = IWX_TFD_QUEUE_CB_SIZE(IWX_TX_RING_COUNT);
cmd.byte_cnt_addr = ring.BcTbl.Phys;
cmd.tfdq_addr = ring.Desc.Phys;
IwxSendCmdPdu(IWX_SCD_QUEUE_CFG, &cmd, sizeof(cmd));
}
IwxResetTxRing(ring);
}
// =========================================================================
// Frame transmission
// =========================================================================
// Lowest usable transmit antenna, as a rate_n_flags antenna field.
static uint32_t IwxTxAntBits() {
uint8_t ant = (uint8_t)((g_iwx.Fw.PhyConfig & IWX_FW_PHY_CFG_TX_CHAIN)
>> IWX_FW_PHY_CFG_TX_CHAIN_POS);
if (g_iwx.Nvm.ValidTxAnt) ant &= g_iwx.Nvm.ValidTxAnt;
if (!ant) ant = 1;
uint8_t lowest = (uint8_t)(ant & (uint8_t)(~ant + 1)); // isolate low bit
return (uint32_t)lowest << IWX_RATE_MCS_ANT_POS;
}
// The lowest basic rate for the current band, in whichever rate_n_flags
// encoding the firmware advertises: 1 Mbps CCK on 2.4 GHz, 6 Mbps OFDM on
// 5 GHz. Management frames go out at this rate because rate control has
// no table for the station until it is associated.
static uint32_t IwxLowestRate() {
uint32_t ant = IwxTxAntBits();
// Firmware exposing TX_CMD notification version 7 or later (equally,
// command version 9+) uses the "version 2" rate layout.
int notifVer = IwxLookupNotifVer(IWX_LONG_GROUP, IWX_TX_CMD);
int cmdVer = IwxLookupCmdVer(IWX_LONG_GROUP, IWX_TX_CMD);
bool v2 = notifVer > 6 || cmdVer >= 9;
if (v2) {
uint32_t mod = g_iwx.Is5GHz ? IWX_RATE_MCS_MOD_LEGACY_OFDM
: IWX_RATE_MCS_MOD_CCK;
return ant | mod | IWX_RATE_MCS_CHAN_WIDTH_20 | 0u; // index 0
}
uint32_t plcp = g_iwx.Is5GHz ? IWX_RATE_6M_PLCP : IWX_RATE_1M_PLCP;
uint32_t cck = g_iwx.Is5GHz ? 0 : IWX_RATE_MCS_CCK_MSK_V1;
return ant | cck | plcp;
}
// Recover a queue whose completions stopped arriving. Without this a
// single lost TX response would permanently consume a slot and, after
// StageSlots of them, wedge the queue.
static uint64_t g_txStallMs = 0;
static bool IwxTxQueueHasRoom(IwxTxRing& ring) {
if (ring.Queued < ring.StageSlots) {
g_txStallMs = 0;
return true;
}
uint64_t now = Timekeeping::GetMilliseconds();
if (g_txStallMs == 0) {
g_txStallMs = now;
return false;
}
if (now - g_txStallMs < 2000) return false;
KernelLogStream(WARNING, "WiFi")
<< "TX queue " << (uint64_t)ring.Qid
<< " stopped completing; resetting its outstanding count";
ring.Queued = 0;
g_txStallMs = 0;
return true;
}
bool IwxTxFrame(IwxTxRing& ring, const uint8_t* hdr, uint32_t hdrLen,
const uint8_t* payload, uint32_t payloadLen,
bool encrypt, bool fixedRate) {
if (g_iwx.State != IwxFwState::Running) return false;
if (!ring.Active || ring.StageSlots == 0) return false;
if (!hdr || hdrLen < IEEE80211_HDR_LEN || hdrLen > 32) return false;
// The 802.11 header is padded to a dword boundary before the payload;
// TX_CMD_OFFLD_PAD tells the firmware to skip those bytes.
uint32_t padded = (hdrLen + 3) & ~3u;
uint32_t head = (uint32_t)(sizeof(IwxCmdHeader) + sizeof(IwxTxCmdGen3)) + padded;
if (head + payloadLen > 4096) return false;
g_iwx.TxLock.Acquire();
if (!IwxTxQueueHasRoom(ring)) {
g_iwx.TxLock.Release();
return false;
}
uint32_t idx = ring.Cur;
uint32_t slot = idx % ring.StageSlots;
uint8_t* buf = ring.Stage[slot];
uint64_t phys = ring.StagePhys[slot];
memset(buf, 0, head);
auto* ch = (IwxCmdHeader*)buf;
ch->code = IWX_TX_CMD;
ch->flags = 0; // TX_CMD stays in the legacy group
ch->idx = (uint8_t)idx;
ch->qid = (uint8_t)ring.Qid;
auto* tx = (IwxTxCmdGen3*)(buf + sizeof(IwxCmdHeader));
tx->len = (uint16_t)(hdrLen + payloadLen);
uint16_t flags = 0;
if (!encrypt) flags |= IWX_TX_FLAGS_ENCRYPT_DIS;
if (fixedRate) {
flags |= IWX_TX_FLAGS_CMD_RATE;
tx->rate_n_flags = IwxLowestRate();
}
tx->flags = flags;
uint32_t offload = ((hdrLen / 2) & IWX_TX_CMD_OFFLD_MH_MASK)
<< IWX_TX_CMD_OFFLD_MH_SIZE_POS;
if (hdrLen % 4) offload |= IWX_TX_CMD_OFFLD_PAD;
tx->offload_assist = offload;
uint8_t* body = buf + sizeof(IwxCmdHeader) + sizeof(IwxTxCmdGen3);
memcpy(body, hdr, hdrLen);
if (payloadLen) memcpy(body + padded, payload, payloadLen);
auto* desc = &((IwxTfhTfd*)ring.Desc.Virt)[idx];
memset(desc, 0, sizeof(*desc));
desc->tbs[0].tb_len = (uint16_t)IWX_FIRST_TB_SIZE;
desc->tbs[0].addr = phys;
desc->tbs[1].tb_len = (uint16_t)(head - IWX_FIRST_TB_SIZE);
desc->tbs[1].addr = phys + IWX_FIRST_TB_SIZE;
uint16_t numTbs = 2;
if (payloadLen) {
desc->tbs[2].tb_len = (uint16_t)payloadLen;
desc->tbs[2].addr = phys + head;
numTbs = 3;
}
desc->num_tbs = numTbs;
// Byte-count table: AX210 wants the frame length in bytes plus the
// number of extra 64-byte chunks the firmware must fetch for the TFD.
uint32_t filled = (uint32_t)(sizeof(uint16_t) + numTbs * sizeof(IwxTfhTb));
uint32_t chunks = ((filled + 63) / 64) - 1;
auto* bc = (IwxGen3BcTblEntry*)ring.BcTbl.Virt;
bc[idx].tfd_offset = (uint16_t)((hdrLen + payloadLen) | (chunks << 14));
asm volatile("" ::: "memory");
ring.Queued++;
ring.Cur = (ring.Cur + 1) % IWX_TX_RING_COUNT;
ring.CurHw = (ring.CurHw + 1) % IWX_TFD_QUEUE_SIZE_MAX_GEN3;
IwxWrite32(IWX_HBUS_TARG_WRPTR, ((uint32_t)ring.Qid << 16) | ring.CurHw);
g_iwx.TxLock.Release();
return true;
}
void IwxTxComplete(int qid, int idx, uint32_t status) {
(void)idx;
IwxTxRing* ring = nullptr;
if (g_iwx.MgmtQ.Active && qid == g_iwx.MgmtQ.Qid) ring = &g_iwx.MgmtQ;
if (!ring) return;
g_iwx.TxLock.Acquire();
if (ring->Queued > 0) ring->Queued--;
g_iwx.TxLock.Release();
if (status == IWX_TX_STATUS_SUCCESS || status == IWX_TX_STATUS_DIRECT_DONE)
g_iwx.TxPackets++;
else
g_iwx.TxFailures++;
}
// =========================================================================
// Hardware key installation
// =========================================================================
// Keys go in and out through SEC_KEY_CMD: the MLD firmware does not
// implement the legacy ADD_STA_KEY, like the rest of the legacy station
// API. Values mirror the Linux trace: PTK as {sta_mask 1, key_id 0,
// flags CIPHER}, GTK as {sta_mask 1, key_id N, flags CIPHER|MCAST}.
static uint32_t SecKeyFlags(uint8_t cipher, uint32_t keyLen, bool pairwise) {
uint32_t flags;
switch (cipher) {
case RSN_CIPHER_CCMP:
case RSN_CIPHER_CCMP_256:
flags = IWX_SEC_KEY_FLAG_CIPHER_CCMP;
break;
case RSN_CIPHER_GCMP:
case RSN_CIPHER_GCMP_256:
flags = IWX_SEC_KEY_FLAG_CIPHER_GCMP;
break;
default:
return 0;
}
if (keyLen == 32) flags |= IWX_SEC_KEY_FLAG_KEY_SIZE;
if (!pairwise) flags |= IWX_SEC_KEY_FLAG_MCAST_KEY;
return flags;
}
bool IwxSetKey(const uint8_t* key, uint32_t keyLen, uint8_t keyIdx,
bool pairwise, uint8_t cipher, const uint8_t* rsc) {
if (!key || (keyLen != 16 && keyLen != 32)) return false;
uint32_t flags = SecKeyFlags(cipher, keyLen, pairwise);
if (!flags) {
KernelLogStream(WARNING, "WiFi")
<< "Cannot install a key for cipher " << (uint64_t)cipher;
return false;
}
IwxSecKeyCmd cmd = {};
cmd.action = IWX_FW_CTXT_ACTION_ADD;
cmd.sta_mask = 1u << IWX_STATION_ID;
cmd.key_id = keyIdx;
cmd.key_flags = flags;
memcpy(cmd.key, key, keyLen);
// The EAPOL RSC carries the AP's packet number for the group key,
// lowest byte first; it becomes the initial receive counter.
if (rsc) {
uint64_t pn = 0;
for (int i = 5; i >= 0; i--) pn = (pn << 8) | rsc[i];
cmd.rx_seq = pn;
}
return IwxSendCmdPdu(IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_SEC_KEY_CMD),
&cmd, sizeof(cmd));
}
bool IwxRemoveKey(uint8_t keyIdx, bool pairwise, uint8_t cipher,
uint32_t keyLen) {
uint32_t flags = SecKeyFlags(cipher, keyLen, pairwise);
if (!flags) return false;
IwxSecKeyCmd cmd = {};
cmd.action = IWX_FW_CTXT_ACTION_REMOVE;
cmd.sta_mask = 1u << IWX_STATION_ID;
cmd.key_id = keyIdx;
cmd.key_flags = flags;
return IwxSendCmdPdu(IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_SEC_KEY_CMD),
&cmd, sizeof(cmd));
}
// =========================================================================
// RX / notification processing
// =========================================================================
@@ -1083,7 +1409,24 @@ namespace Drivers::Net::Wifi {
// between them they pin down which host command the firmware rejected.
void IwxDumpFwError() {
KernelLogStream(ERROR, "WiFi-FW") << "Firmware assert; last command sent: 0x"
<< base::hex << (uint64_t)g_iwx.LastCmdId << base::dec;
<< base::hex << (uint64_t)g_iwx.LastCmdId << base::dec
<< " (" << (uint64_t)g_iwx.LastCmdLen << " byte payload)";
// Dump the payload: a struct that does not match the firmware's
// expected layout is otherwise impossible to spot from the log.
{
uint32_t n = g_iwx.LastCmdLen;
if (n > sizeof(g_iwx.LastCmdPayload)) n = sizeof(g_iwx.LastCmdPayload);
for (uint32_t off = 0; off < n; off += 32) {
auto line = KernelLogStream(INFO, "WiFi-FW");
line << " +" << (uint64_t)off << ": " << base::hex;
for (uint32_t i = off; i < n && i < off + 32; i++) {
if (g_iwx.LastCmdPayload[i] < 0x10) line << "0";
line << (uint64_t)g_iwx.LastCmdPayload[i];
}
line << base::dec;
}
}
uint32_t base_ = g_iwx.UmacErrorTable;
if (base_ < 0x400000) {
@@ -1141,6 +1484,16 @@ namespace Drivers::Net::Wifi {
case IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP, IWX_PNVM_INIT_COMPLETE):
g_iwx.InitComplete |= 0x2;
break;
case IWX_TX_CMD: {
// TX completion for a frame we queued on a data/mgmt queue.
uint32_t status = 0;
if (IwxRxPacketPayloadLen(pkt) >= sizeof(IwxTxResp)) {
auto* r = (const IwxTxResp*)pkt->data;
status = r->status & IWX_TX_STATUS_MSK;
}
IwxTxComplete(qid & ~0x80, pkt->hdr.idx, status);
break;
}
case IWX_REPLY_ERROR: {
if (IwxRxPacketPayloadLen(pkt) >= 8) {
uint32_t errType = *(const uint32_t*)pkt->data;
@@ -1208,8 +1561,9 @@ namespace Drivers::Net::Wifi {
void IwxProcessEvents() {
if (!g_iwx.Mmio) return;
if (g_iwx.InProcessEvents) return; // never nest
g_iwx.InProcessEvents = true;
// Never nest. With interrupts enabled during command waits this is a
// genuine multi-core race, so it has to be an atomic test-and-set.
if (g_iwx.InProcessEvents.test_and_set(std::memory_order_acquire)) return;
if (g_msix) {
uint32_t fh = IwxRead32(IWX_CSR_MSIX_FH_INT_CAUSES_AD);
@@ -1276,7 +1630,7 @@ namespace Drivers::Net::Wifi {
IwxNotifIntr();
g_iwx.WorkPending = false;
g_iwx.InProcessEvents = false;
g_iwx.InProcessEvents.clear(std::memory_order_release);
}
// =========================================================================
@@ -1435,7 +1789,8 @@ namespace Drivers::Net::Wifi {
|| !IwxDmaAlloc(g_iwx.PrphInfo, 4096)
|| !IwxAllocRxRing()
|| !IwxAllocTxRing(g_iwx.CmdQ, IWX_DQA_CMD_QUEUE)
|| !IwxAllocTxRing(g_iwx.MgmtQ, IWX_DQA_MGMT_QUEUE)) {
|| !IwxAllocTxRing(g_iwx.MgmtQ, IWX_DQA_MGMT_QUEUE,
IWX_TX_STAGE_SLOTS)) {
KernelLogStream(ERROR, "WiFi") << "Could not allocate device DMA memory";
IwxFreeRxRing();
IwxFreeTxRing(g_iwx.CmdQ);
+173 -32
View File
@@ -13,6 +13,7 @@
#include "Wifi.hpp"
#include "Iwx.hpp"
#include "Wpa.hpp"
#include <Fs/Vfs.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
@@ -20,6 +21,7 @@
#include <CppLib/Spinlock.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Hal/SmpBoot.hpp>
#include <Sched/Scheduler.hpp>
#include <atomic>
using namespace Kt;
@@ -36,6 +38,10 @@ namespace Drivers::Net::Wifi {
static constexpr int MAX_SCAN_RESULTS = 64;
// Long enough for a personal-mode RSN element: version, group cipher, one
// or two pairwise ciphers, a handful of AKMs and the capability field.
static constexpr int MAX_RSN_IE = 64;
struct ScanEntry {
uint8_t Bssid[6];
char Ssid[33];
@@ -45,6 +51,9 @@ namespace Drivers::Net::Wifi {
uint8_t Band; // 0 = 2.4 GHz, 1 = 5 GHz
uint8_t Security; // WifiSecurity value
uint16_t BeaconInterval;
uint8_t DtimPeriod;
uint8_t RsnIe[MAX_RSN_IE]; // element body, without id/len
uint8_t RsnIeLen;
bool Used;
};
@@ -71,6 +80,7 @@ namespace Drivers::Net::Wifi {
// Element IDs used here.
static constexpr uint8_t ELEMID_SSID = 0;
static constexpr uint8_t ELEMID_DSPARMS = 3;
static constexpr uint8_t ELEMID_TIM = 5;
static constexpr uint8_t ELEMID_RSN = 48;
static constexpr uint8_t ELEMID_VENDOR = 221;
@@ -108,6 +118,9 @@ namespace Drivers::Net::Wifi {
uint8_t Channel = 0;
uint8_t Security = WIFI_SEC_OPEN;
uint16_t BeaconInterval = 0;
uint8_t DtimPeriod = 0;
const uint8_t* Rsn = nullptr; // RSN element body
uint8_t RsnLen = 0;
};
static bool ParseBeacon(const uint8_t* frame, uint32_t len, ParsedBeacon* out) {
@@ -139,9 +152,18 @@ namespace Drivers::Net::Wifi {
case ELEMID_DSPARMS:
if (ielen >= 1) out->Channel = body[0];
break;
case ELEMID_TIM:
// DTIM count, then DTIM period. The firmware needs the
// period to schedule wake-ups once associated.
if (ielen >= 2) out->DtimPeriod = body[1];
break;
case ELEMID_RSN:
haveRsn = true;
out->Security = ClassifyRsn(body, ielen);
// Kept verbatim: the connect path negotiates ciphers and
// AKMs straight out of it.
out->Rsn = body;
out->RsnLen = ielen;
break;
case ELEMID_VENDOR:
// WPA1: Microsoft OUI 00:50:F2, type 1.
@@ -220,6 +242,12 @@ namespace Drivers::Net::Wifi {
slot->Band = ch > 14 ? 1 : 0;
slot->Security = pb.Security;
slot->BeaconInterval = pb.BeaconInterval;
if (pb.DtimPeriod) slot->DtimPeriod = pb.DtimPeriod;
slot->RsnIeLen = 0;
if (pb.Rsn && pb.RsnLen && pb.RsnLen <= MAX_RSN_IE) {
memcpy(slot->RsnIe, pb.Rsn, pb.RsnLen);
slot->RsnIeLen = pb.RsnLen;
}
g_resultLock.Release();
}
@@ -371,8 +399,18 @@ namespace Drivers::Net::Wifi {
| (g_iwx.Nvm.Sku52GHz ? 2 : 0));
out->channels = (uint16_t)g_iwx.ChannelCount;
out->rxPackets = g_iwx.RxPackets;
out->txPackets = g_iwx.TxPackets;
out->fwErrors = (uint32_t)g_iwx.FwErrors;
out->connState = (uint32_t)IwxConnectState();
out->connected = IwxLinkUp() ? 1 : 0;
if (IwxConnectState() != (int)IwxConnStateId::Idle) {
const char* ssid = IwxConnectSsid();
int k = 0;
for (; k < 32 && ssid[k]; k++) out->ssid[k] = ssid[k];
out->ssid[k] = '\0';
memcpy(out->bssid, IwxConnectBssid(), 6);
}
int i = 0;
for (; i < 31 && g_iwx.Fw.Version[i]; i++) out->fwVersion[i] = g_iwx.Fw.Version[i];
@@ -380,55 +418,136 @@ namespace Drivers::Net::Wifi {
return 0;
}
// Wait for the association + handshake to settle. The whole exchange is
// driven from IwxConnectService(), so this pumps both while it waits
// instead of relying on the idle loop getting scheduled.
static int WaitForConnection(uint32_t timeoutMs) {
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
IwxProcessEvents();
IwxConnectService();
auto state = (IwxConnStateId)IwxConnectState();
if (state == IwxConnStateId::Connected) return 0;
if (state == IwxConnStateId::Failed) {
// A handshake that got as far as exchanging EAPOL frames and
// then failed is almost always a wrong passphrase.
int rc = WpaGetState() == WpaState::Failed
? WIFI_ERR_AUTH : WIFI_ERR_FAILED;
// A rejection seen on the RX path only marks the state; the
// firmware contexts are still up and have to come back down.
IwxConnectAbort();
return rc;
}
if (state == IwxConnStateId::Idle) return WIFI_ERR_FAILED;
if (g_iwx.State == IwxFwState::Error) {
IwxConnectAbort();
return WIFI_ERR_FAILED;
}
// Yield rather than burn the core: this can wait seconds, and the
// idle loop on other cores drives the same state machine.
Sched::Schedule();
}
IwxConnectAbort();
return WIFI_ERR_TIMEOUT;
}
int Connect(const char* ssid, const char* password) {
if (!g_initialized || !ssid) return -1;
if (!g_initialized || !ssid) return WIFI_ERR_NO_ADAPTER;
if (g_iwx.State != IwxFwState::Running) return WIFI_ERR_NO_ADAPTER;
// Locate the network in the most recent scan results: the firmware
// contexts need its BSSID and channel.
// contexts need its BSSID and channel, and the connect path needs its
// RSN element to negotiate ciphers.
uint8_t bssid[6];
uint8_t channel = 0;
bool is5 = false;
uint8_t security = WIFI_SEC_OPEN;
uint8_t rsnIe[MAX_RSN_IE];
uint8_t rsnIeLen = 0;
uint16_t beaconInterval = 0;
uint8_t dtimPeriod = 0;
bool found = false;
g_resultLock.Acquire();
for (int i = 0; i < MAX_SCAN_RESULTS; i++) {
if (!g_results[i].Used) continue;
const ScanEntry& e = g_results[i];
bool match = true;
for (int k = 0; k < 32; k++) {
char a = e.Ssid[k], b = ssid[k];
if (a != b) { match = false; break; }
if (a == '\0') break;
// Strongest first, so a network seen on several bands or repeaters is
// joined through the best AP rather than whichever answered first.
int8_t bestRssi = -128;
for (int attempt = 0; attempt < 2 && !found; attempt++) {
if (attempt == 1) {
// Nothing matched: the caller may never have scanned, or the
// results may predate this network appearing.
KernelLogStream(INFO, "WiFi")
<< "\"" << ssid << "\" is not in the scan results; scanning again";
WifiNetwork tmp[1];
Scan(tmp, 1, 4000);
}
if (!match) continue;
memcpy(bssid, e.Bssid, 6);
channel = e.Channel;
is5 = e.Band == 1;
security = e.Security;
found = true;
break;
}
g_resultLock.Release();
if (!found) {
g_resultLock.Acquire();
for (int i = 0; i < MAX_SCAN_RESULTS; i++) {
if (!g_results[i].Used) continue;
const ScanEntry& e = g_results[i];
bool match = true;
for (int k = 0; k < 32; k++) {
char a = e.Ssid[k], b = ssid[k];
if (a != b) { match = false; break; }
if (a == '\0') break;
}
if (!match) continue;
if (found && e.Rssi <= bestRssi) continue;
memcpy(bssid, e.Bssid, 6);
channel = e.Channel;
is5 = e.Band == 1;
security = e.Security;
rsnIeLen = e.RsnIeLen;
if (rsnIeLen) memcpy(rsnIe, e.RsnIe, rsnIeLen);
beaconInterval = e.BeaconInterval;
dtimPeriod = e.DtimPeriod;
bestRssi = e.Rssi;
found = true;
}
g_resultLock.Release();
}
if (!found) return WIFI_ERR_NOT_FOUND;
bool havePassword = password && password[0];
// WEP and WPA1 use RC4/TKIP, which the supplicant deliberately does
// not implement; say so rather than failing mid-handshake.
if (security == WIFI_SEC_WEP || security == WIFI_SEC_WPA) {
KernelLogStream(WARNING, "WiFi")
<< "Network not in scan results; run a scan first";
return -1;
<< "WEP and the original WPA use ciphers this driver does not implement";
return WIFI_ERR_UNSUPPORTED;
}
// Encryption is checked before the passphrase: the WPA2/WPA3 key
// exchange (PMK derivation, EAPOL 4-way, HW key install) is not
// implemented at all, so a passphrase would not help and reporting
// "needs a passphrase" would be misleading.
if (security != WIFI_SEC_OPEN) {
KernelLogStream(WARNING, "WiFi")
<< "Encrypted networks are not supported yet (open only)";
return -2;
if (!rsnIeLen) {
KernelLogStream(WARNING, "WiFi")
<< "The beacon for this network carried no RSN element";
return WIFI_ERR_UNSUPPORTED;
}
if (!havePassword) return WIFI_ERR_NEED_KEY;
}
(void)password;
return IwxConnectStart(bssid, channel, is5, ssid) ? 0 : -1;
if (!IwxConnectStart(bssid, channel, is5, ssid,
havePassword ? password : nullptr,
security == WIFI_SEC_OPEN ? nullptr : rsnIe,
security == WIFI_SEC_OPEN ? 0 : rsnIeLen,
beaconInterval, dtimPeriod)) {
// IwxConnectStart already logged the specific reason. Only report
// a security problem when that is genuinely what happened; a radio
// or firmware failure needs completely different advice.
bool security_ = IwxConnectRefusedForSecurity();
auto state = (IwxConnStateId)IwxConnectState();
if (state != IwxConnStateId::Idle) IwxConnectAbort();
return security_ ? WIFI_ERR_UNSUPPORTED : WIFI_ERR_FAILED;
}
return WaitForConnection(15000);
}
int Disconnect() {
@@ -436,4 +555,26 @@ namespace Drivers::Net::Wifi {
IwxConnectAbort();
return 0;
}
// =========================================================================
// Network interface
// =========================================================================
static RxCallback g_rxCallback = nullptr;
const uint8_t* GetMacAddress() { return g_iwx.Nvm.HwAddr; }
bool IsLinkUp() { return g_initialized && IwxLinkUp(); }
bool SendPacket(const uint8_t* data, uint16_t length) {
if (!IsLinkUp()) return false;
return IwxConnectSendEthernet(data, length);
}
void SetRxCallback(RxCallback callback) { g_rxCallback = callback; }
// Called by the connect path for every decapsulated Ethernet frame.
void WifiRxEthernet(const uint8_t* frame, uint32_t len) {
if (g_rxCallback && len <= 0xffff) g_rxCallback(frame, (uint16_t)len);
}
}
+18 -3
View File
@@ -32,9 +32,24 @@ namespace Drivers::Net::Wifi {
// Fill in adapter/firmware status.
int GetInfo(montauk::abi::WifiInfo* out);
// Association groundwork. Returns 0 when the firmware contexts came up,
// negative on failure. See IwxConnect.cpp: the 802.11 handshake itself is
// not implemented yet, so this cannot establish a usable link.
// Join a network. Blocks (pumping firmware events) until the link is up
// or the attempt fails. Returns 0 on success, or a negative WIFI_ERR_*
// value describing why it could not connect.
int Connect(const char* ssid, const char* password);
int Disconnect();
// -------------------------------------------------------------------------
// Network interface, registered with Net::NetIf once associated
// -------------------------------------------------------------------------
const uint8_t* GetMacAddress();
// Send an Ethernet frame. Fails while the link is down.
bool SendPacket(const uint8_t* data, uint16_t length);
// True once a network has been joined and, for encrypted networks, keyed.
bool IsLinkUp();
using RxCallback = void(*)(const uint8_t* data, uint16_t length);
void SetRxCallback(RxCallback callback);
}
+830
View File
@@ -0,0 +1,830 @@
/*
* Wpa.cpp
* WPA2/WPA3-PSK supplicant: PMK derivation, the EAPOL-Key 4-way handshake
* and the group-key handshake used for periodic GTK rekeying.
*
* Supported: RSN (WPA2) with CCMP or GCMP, AKM PSK (00-0F-AC:2) and
* PSK-SHA256 (:6). Key descriptor versions 2 (HMAC-SHA1-128 MIC, AES key
* wrap) and 3 (AES-128-CMAC MIC) are handled.
*
* Not supported: SAE (WPA3 needs finite-field / elliptic-curve crypto that
* is well beyond what belongs in this kernel), TKIP and WEP (descriptor
* version 1 needs HMAC-MD5 and RC4, and the ciphers are broken anyway), and
* 802.1X/EAP enterprise authentication. Those are rejected up front with a
* clear reason rather than failing halfway through the handshake.
*
* Copyright (c) 2026 Daniel Hammer
*/
#include "Wpa.hpp"
#include "Ieee80211.hpp"
#include <Libraries/Crypto.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Timekeeping/ApicTimer.hpp>
using namespace Kt;
namespace Drivers::Net::Wifi {
// =========================================================================
// EAPOL-Key frame layout (IEEE 802.1X-2004 + IEEE 802.11 key descriptor)
// =========================================================================
constexpr uint8_t EAPOL_TYPE_KEY = 3;
constexpr uint8_t EAPOL_KEY_DESC_RSN = 2;
constexpr uint8_t EAPOL_KEY_DESC_WPA = 254;
constexpr uint16_t KEY_INFO_VERSION_MASK = 0x0007;
constexpr uint16_t KEY_INFO_KEY_TYPE = 0x0008; // set = pairwise
constexpr uint16_t KEY_INFO_INSTALL = 0x0040;
constexpr uint16_t KEY_INFO_ACK = 0x0080;
constexpr uint16_t KEY_INFO_MIC = 0x0100;
constexpr uint16_t KEY_INFO_SECURE = 0x0200;
constexpr uint16_t KEY_INFO_ERROR = 0x0400;
constexpr uint16_t KEY_INFO_REQUEST = 0x0800;
constexpr uint16_t KEY_INFO_ENCRYPTED = 0x1000;
// Key descriptor versions: 1 is HMAC-MD5 + RC4 (unsupported), 2 is
// HMAC-SHA1-128 + AES key wrap, 3 is AES-128-CMAC + AES key wrap.
constexpr uint8_t KEY_DESC_VER_RC4 = 1;
constexpr uint8_t KEY_DESC_VER_AES = 2;
constexpr uint8_t KEY_DESC_VER_AES_CMAC = 3;
struct EapolKey {
uint8_t version;
uint8_t type;
uint8_t length[2]; // big endian, bytes after this field
uint8_t descType;
uint8_t keyInfo[2]; // big endian
uint8_t keyLength[2]; // big endian
uint8_t replay[8];
uint8_t nonce[32];
uint8_t iv[16];
uint8_t rsc[8];
uint8_t keyId[8];
uint8_t mic[16];
uint8_t keyDataLen[2]; // big endian
// key data follows
} __attribute__((packed));
static_assert(sizeof(EapolKey) == 99, "EAPOL-Key header must stay 99 bytes");
constexpr uint32_t EAPOL_MIC_LEN = 16;
constexpr uint32_t MAX_KEY_DATA = 512;
constexpr uint32_t MAX_EAPOL_TX = sizeof(EapolKey) + 128;
// =========================================================================
// Supplicant state
// =========================================================================
static WpaConfig g_cfg = {};
static WpaState g_state = WpaState::Idle;
static uint8_t g_pmk[32];
static uint8_t g_ptk[64]; // KCK | KEK | TK
static uint32_t g_kckLen = 16;
static uint32_t g_kekLen = 16;
static uint32_t g_tkLen = 16;
static uint8_t g_anonce[32];
static uint8_t g_snonce[32];
static uint8_t g_replay[8];
static bool g_haveReplay = false;
static uint8_t g_keyDescVer = KEY_DESC_VER_AES;
static uint8_t g_descType = EAPOL_KEY_DESC_RSN;
static uint8_t g_eapolVersion = 2;
static uint8_t g_rsnIe[32];
static uint32_t g_rsnIeLen = 0;
// Retransmission of the last message we sent. The AP retries msg 1 and 3
// on its own, but a lost msg 2 or 4 otherwise stalls the exchange until the
// AP gives up and deauthenticates.
static uint8_t g_lastTx[MAX_EAPOL_TX];
static uint32_t g_lastTxLen = 0;
static uint64_t g_lastTxMs = 0;
static int g_retries = 0;
static uint64_t g_startMs = 0;
static constexpr uint64_t RETRY_INTERVAL_MS = 500;
static constexpr int MAX_RETRIES = 4;
static constexpr uint64_t HANDSHAKE_TIMEOUT_MS = 5000;
// =========================================================================
// Nonce generation
// =========================================================================
// A SNonce only has to be unique per (PMK, ANonce) pair, but making it
// unpredictable costs nothing: fold a run of TSC samples into SHA-256 along
// with our MAC and a monotonic counter. RDRAND is deliberately avoided --
// see Api/Random.hpp for why it is unreliable on this hardware.
static void GenNonce(uint8_t out[32]) {
static uint64_t counter = 0;
struct {
uint64_t tsc[8];
uint64_t counter;
uint8_t mac[6];
uint8_t pad[2];
} pool;
for (int i = 0; i < 8; i++) {
uint64_t tsc;
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax"
: "=a"(tsc) :: "rdx");
pool.tsc[i] = tsc;
// Space the samples out so the low bits differ between them.
for (int k = 0; k < 64; k++) asm volatile("pause" ::: "memory");
}
pool.counter = ++counter;
memcpy(pool.mac, g_cfg.OwnMac, 6);
pool.pad[0] = pool.pad[1] = 0;
Crypto::Sha256(&pool, sizeof(pool), out);
}
// =========================================================================
// Key derivation
// =========================================================================
// IEEE 802.11 PRF-N built on HMAC-SHA1 (used by AKM PSK).
static void Prf(const uint8_t* key, uint32_t keyLen, const char* label,
const uint8_t* data, uint32_t dataLen,
uint8_t* out, uint32_t outLen) {
uint32_t labelLen = 0;
while (label[labelLen]) labelLen++;
uint8_t counter = 0;
uint32_t pos = 0;
const uint8_t zero = 0;
while (pos < outLen) {
const uint8_t* parts[4] = {
(const uint8_t*)label, &zero, data, &counter
};
size_t lens[4] = { labelLen, 1, dataLen, 1 };
uint8_t digest[Crypto::SHA1_DIGEST_SIZE];
Crypto::HmacSha1(key, keyLen, parts, lens, 4, digest);
uint32_t take = outLen - pos;
if (take > Crypto::SHA1_DIGEST_SIZE) take = Crypto::SHA1_DIGEST_SIZE;
memcpy(out + pos, digest, take);
pos += take;
counter++;
Crypto::SecureZero(digest, sizeof(digest));
}
}
// IEEE 802.11 KDF built on HMAC-SHA256 (used by AKM PSK-SHA256 and SAE).
static void KdfSha256(const uint8_t* key, uint32_t keyLen, const char* label,
const uint8_t* data, uint32_t dataLen,
uint8_t* out, uint32_t outLen) {
uint32_t labelLen = 0;
while (label[labelLen]) labelLen++;
uint16_t bits = (uint16_t)(outLen * 8);
uint8_t lenLe[2] = { (uint8_t)bits, (uint8_t)(bits >> 8) };
uint16_t iter = 1;
uint32_t pos = 0;
while (pos < outLen) {
uint8_t iterLe[2] = { (uint8_t)iter, (uint8_t)(iter >> 8) };
const uint8_t* parts[4] = {
iterLe, (const uint8_t*)label, data, lenLe
};
size_t lens[4] = { 2, labelLen, dataLen, 2 };
uint8_t digest[Crypto::SHA256_DIGEST_SIZE];
Crypto::HmacSha256(key, keyLen, parts, lens, 4, digest);
uint32_t take = outLen - pos;
if (take > Crypto::SHA256_DIGEST_SIZE) take = Crypto::SHA256_DIGEST_SIZE;
memcpy(out + pos, digest, take);
pos += take;
iter++;
Crypto::SecureZero(digest, sizeof(digest));
}
}
static bool UsesSha256Kdf(uint8_t akm) {
return akm == RSN_AKM_PSK_SHA256 || akm == RSN_AKM_SAE
|| akm == RSN_AKM_FT_SAE;
}
// PTK = KDF(PMK, "Pairwise key expansion",
// min(AA,SPA) || max(AA,SPA) || min(ANonce,SNonce) || max(...))
static void DerivePtk() {
const uint8_t* aa = g_cfg.Bssid;
const uint8_t* spa = g_cfg.OwnMac;
uint8_t data[76];
int cmp = memcmp(aa, spa, 6);
const uint8_t* lo = (cmp < 0) ? aa : spa;
const uint8_t* hi = (cmp < 0) ? spa : aa;
memcpy(data, lo, 6);
memcpy(data + 6, hi, 6);
cmp = memcmp(g_anonce, g_snonce, 32);
const uint8_t* nlo = (cmp < 0) ? g_anonce : g_snonce;
const uint8_t* nhi = (cmp < 0) ? g_snonce : g_anonce;
memcpy(data + 12, nlo, 32);
memcpy(data + 44, nhi, 32);
uint32_t ptkLen = g_kckLen + g_kekLen + g_tkLen;
if (UsesSha256Kdf(g_cfg.Akm))
KdfSha256(g_pmk, 32, "Pairwise key expansion", data, sizeof(data),
g_ptk, ptkLen);
else
Prf(g_pmk, 32, "Pairwise key expansion", data, sizeof(data),
g_ptk, ptkLen);
Crypto::SecureZero(data, sizeof(data));
}
static const uint8_t* Kck() { return g_ptk; }
static const uint8_t* Kek() { return g_ptk + g_kckLen; }
static const uint8_t* Tk() { return g_ptk + g_kckLen + g_kekLen; }
// =========================================================================
// MIC
// =========================================================================
// The MIC covers the whole 802.1X frame with the MIC field zeroed.
static void ComputeMic(const uint8_t* frame, uint32_t len, uint8_t out[16]) {
if (g_keyDescVer == KEY_DESC_VER_AES_CMAC) {
const uint8_t* parts[1] = { frame };
size_t lens[1] = { len };
Crypto::AesCmac(Kck(), g_kckLen, parts, lens, 1, out);
} else {
uint8_t digest[Crypto::SHA1_DIGEST_SIZE];
Crypto::HmacSha1(Kck(), g_kckLen, frame, len, digest);
memcpy(out, digest, 16);
Crypto::SecureZero(digest, sizeof(digest));
}
}
static bool VerifyMic(const uint8_t* frame, uint32_t len) {
// Copy so the received frame can keep its MIC for logging.
static uint8_t scratch[sizeof(EapolKey) + MAX_KEY_DATA];
if (len > sizeof(scratch)) return false;
memcpy(scratch, frame, len);
auto* k = (EapolKey*)scratch;
uint8_t received[16];
memcpy(received, k->mic, 16);
memset(k->mic, 0, 16);
uint8_t computed[16];
ComputeMic(scratch, len, computed);
return Crypto::SecureEqual(received, computed, 16);
}
// =========================================================================
// RSN information element
// =========================================================================
static void PutSuite(uint8_t* p, uint8_t type) {
p[0] = 0x00; p[1] = 0x0f; p[2] = 0xac; p[3] = type;
}
static bool IsRsnSuite(const uint8_t* p) {
return p[0] == 0x00 && p[1] == 0x0f && p[2] == 0xac;
}
uint32_t WpaBuildRsnIe(uint8_t* out, uint32_t cap) {
constexpr uint32_t BODY = 20;
if (cap < BODY + 2) return 0;
uint8_t* p = out;
*p++ = IEEE80211_ELEMID_RSN;
*p++ = (uint8_t)BODY;
Put16Le(p, 1); p += 2; // RSN version
PutSuite(p, g_cfg.GroupCipher); p += 4;
Put16Le(p, 1); p += 2; // one pairwise cipher
PutSuite(p, g_cfg.PairwiseCipher); p += 4;
Put16Le(p, 1); p += 2; // one AKM
PutSuite(p, g_cfg.Akm); p += 4;
// RSN capabilities: no PMF (BIP/IGTK is not implemented), no preauth,
// one replay counter per key.
Put16Le(p, 0); p += 2;
return (uint32_t)(p - out);
}
bool WpaParseApRsn(const uint8_t* ie, uint32_t len, WpaConfig& cfg) {
if (len < 8) return false;
uint32_t off = 0;
uint16_t version = Get16Le(ie);
off += 2;
if (version != 1) return false;
if (off + 4 > len) return false;
uint8_t groupCipher = RSN_CIPHER_CCMP;
if (IsRsnSuite(ie + off)) groupCipher = ie[off + 3];
off += 4;
if (off + 2 > len) return false;
uint16_t pairwiseCount = Get16Le(ie + off);
off += 2;
// Prefer CCMP; fall back to GCMP. TKIP-only networks are rejected.
int bestPairwise = -1;
for (uint16_t i = 0; i < pairwiseCount && off + 4 <= len; i++, off += 4) {
if (!IsRsnSuite(ie + off)) continue;
uint8_t c = ie[off + 3];
if (c == RSN_CIPHER_CCMP) bestPairwise = c;
else if (c == RSN_CIPHER_GCMP && bestPairwise < 0) bestPairwise = c;
else if (c == RSN_CIPHER_GCMP_256 && bestPairwise < 0) bestPairwise = c;
else if (c == RSN_CIPHER_CCMP_256 && bestPairwise < 0) bestPairwise = c;
}
if (bestPairwise < 0) {
KernelLogStream(WARNING, "WiFi")
<< "AP offers no supported pairwise cipher (CCMP/GCMP required)";
return false;
}
if (off + 2 > len) return false;
uint16_t akmCount = Get16Le(ie + off);
off += 2;
// Prefer plain PSK; PSK-SHA256 works too but needs the SHA-256 KDF.
int bestAkm = -1;
bool sawSae = false;
for (uint16_t i = 0; i < akmCount && off + 4 <= len; i++, off += 4) {
if (!IsRsnSuite(ie + off)) continue;
uint8_t a = ie[off + 3];
if (a == RSN_AKM_SAE || a == RSN_AKM_FT_SAE) sawSae = true;
if (a == RSN_AKM_PSK) bestAkm = a;
else if (a == RSN_AKM_PSK_SHA256 && bestAkm < 0) bestAkm = a;
}
if (bestAkm < 0) {
if (sawSae)
KernelLogStream(WARNING, "WiFi")
<< "Network is WPA3-only (SAE); SAE authentication is not implemented";
else
KernelLogStream(WARNING, "WiFi")
<< "AP offers no pre-shared-key AKM (enterprise 802.1X is not supported)";
return false;
}
uint16_t rsnCaps = 0;
if (off + 2 <= len) rsnCaps = Get16Le(ie + off);
if (rsnCaps & RSN_CAP_MFPR) {
KernelLogStream(WARNING, "WiFi")
<< "AP requires management frame protection, which needs BIP; not supported";
return false;
}
// A mixed WPA/WPA2 network can pair with CCMP but still broadcast under
// TKIP. The firmware key slot and the RX decryption check both only
// handle CCMP/GCMP, so the group key would silently fail to install and
// every broadcast frame -- ARP requests, broadcast DHCP replies --
// would be dropped. Refuse up front instead of half connecting.
if (groupCipher != RSN_CIPHER_CCMP && groupCipher != RSN_CIPHER_GCMP
&& groupCipher != RSN_CIPHER_CCMP_256
&& groupCipher != RSN_CIPHER_GCMP_256) {
KernelLogStream(WARNING, "WiFi")
<< "AP broadcasts with an unsupported group cipher ("
<< (uint64_t)groupCipher
<< "); this is usually a mixed WPA/WPA2 network still using TKIP";
return false;
}
cfg.GroupCipher = groupCipher;
cfg.PairwiseCipher = (uint8_t)bestPairwise;
cfg.Akm = (uint8_t)bestAkm;
cfg.Mfp = false;
return true;
}
// =========================================================================
// Start / reset
// =========================================================================
static bool HexNibble(char c, uint8_t* out) {
if (c >= '0' && c <= '9') { *out = (uint8_t)(c - '0'); return true; }
if (c >= 'a' && c <= 'f') { *out = (uint8_t)(c - 'a' + 10); return true; }
if (c >= 'A' && c <= 'F') { *out = (uint8_t)(c - 'A' + 10); return true; }
return false;
}
// A 64-character hex string is the raw 256-bit PSK; anything else is a
// passphrase and goes through PBKDF2 with the SSID as salt.
static bool DerivePmk() {
if (g_cfg.PassLen == 64) {
bool allHex = true;
uint8_t tmp[32];
for (int i = 0; i < 32; i++) {
uint8_t hi, lo;
if (!HexNibble(g_cfg.Passphrase[i * 2], &hi)
|| !HexNibble(g_cfg.Passphrase[i * 2 + 1], &lo)) {
allHex = false;
break;
}
tmp[i] = (uint8_t)((hi << 4) | lo);
}
if (allHex) {
memcpy(g_pmk, tmp, 32);
Crypto::SecureZero(tmp, sizeof(tmp));
return true;
}
}
if (g_cfg.PassLen < 8) {
KernelLogStream(WARNING, "WiFi")
<< "WPA passphrases must be at least 8 characters";
return false;
}
// 4096 iterations of HMAC-SHA1 over a 32-byte output: this is a couple
// of hundred milliseconds of pure CPU, once per connect.
Crypto::Pbkdf2Sha1(g_cfg.Passphrase, g_cfg.PassLen,
g_cfg.Ssid, g_cfg.SsidLen, 4096, g_pmk, 32);
return true;
}
void WpaReset() {
g_state = WpaState::Idle;
g_haveReplay = false;
g_lastTxLen = 0;
g_retries = 0;
Crypto::SecureZero(g_pmk, sizeof(g_pmk));
Crypto::SecureZero(g_ptk, sizeof(g_ptk));
Crypto::SecureZero(g_snonce, sizeof(g_snonce));
Crypto::SecureZero(&g_cfg.Passphrase, sizeof(g_cfg.Passphrase));
}
bool WpaStart(const WpaConfig& cfg) {
WpaReset();
g_cfg = cfg;
switch (g_cfg.PairwiseCipher) {
case RSN_CIPHER_CCMP:
case RSN_CIPHER_GCMP:
g_tkLen = 16;
break;
case RSN_CIPHER_CCMP_256:
case RSN_CIPHER_GCMP_256:
g_tkLen = 32;
break;
default:
KernelLogStream(WARNING, "WiFi")
<< "Unsupported pairwise cipher " << (uint64_t)g_cfg.PairwiseCipher;
return false;
}
g_kckLen = 16;
g_kekLen = 16;
if (!DerivePmk()) return false;
g_rsnIeLen = WpaBuildRsnIe(g_rsnIe, sizeof(g_rsnIe));
if (!g_rsnIeLen) return false;
GenNonce(g_snonce);
g_state = WpaState::WaitMsg1;
g_startMs = 0;
KernelLogStream(INFO, "WiFi") << "Starting WPA handshake (AKM "
<< (uint64_t)g_cfg.Akm << ", pairwise cipher "
<< (uint64_t)g_cfg.PairwiseCipher << ")";
return true;
}
WpaState WpaGetState() { return g_state; }
bool WpaIsComplete() { return g_state == WpaState::Complete; }
// =========================================================================
// Outbound messages
// =========================================================================
static bool SendKeyFrame(uint16_t keyInfo, const uint8_t* keyData,
uint32_t keyDataLen, const uint8_t* nonce) {
uint32_t total = sizeof(EapolKey) + keyDataLen;
if (total > sizeof(g_lastTx)) return false;
memset(g_lastTx, 0, total);
auto* k = (EapolKey*)g_lastTx;
k->version = g_eapolVersion;
k->type = EAPOL_TYPE_KEY;
Put16Be(k->length, (uint16_t)(total - 4));
k->descType = g_descType;
Put16Be(k->keyInfo, keyInfo);
Put16Be(k->keyLength, 0); // RSN: always zero from the STA
memcpy(k->replay, g_replay, 8);
if (nonce) memcpy(k->nonce, nonce, 32);
Put16Be(k->keyDataLen, (uint16_t)keyDataLen);
if (keyDataLen) memcpy(g_lastTx + sizeof(EapolKey), keyData, keyDataLen);
if (keyInfo & KEY_INFO_MIC) ComputeMic(g_lastTx, total, k->mic);
g_lastTxLen = total;
g_lastTxMs = Timekeeping::GetMilliseconds();
g_retries = 0;
return WpaTxEapol(g_lastTx, total);
}
static bool SendMsg2() {
uint16_t keyInfo = (uint16_t)(g_keyDescVer | KEY_INFO_KEY_TYPE
| KEY_INFO_MIC);
return SendKeyFrame(keyInfo, g_rsnIe, g_rsnIeLen, g_snonce);
}
static bool SendMsg4() {
uint16_t keyInfo = (uint16_t)(g_keyDescVer | KEY_INFO_KEY_TYPE
| KEY_INFO_MIC | KEY_INFO_SECURE);
return SendKeyFrame(keyInfo, nullptr, 0, nullptr);
}
// Group key handshake reply: same shape as msg 4 without the pairwise bit.
static bool SendGroupAck() {
uint16_t keyInfo = (uint16_t)(g_keyDescVer | KEY_INFO_MIC
| KEY_INFO_SECURE);
return SendKeyFrame(keyInfo, nullptr, 0, nullptr);
}
// =========================================================================
// Key data (KDE) handling
// =========================================================================
// Decrypt the key data field of msg 3 or a group-key message. Returns the
// plaintext length, or 0 on failure.
static uint32_t DecryptKeyData(const uint8_t* in, uint32_t inLen,
uint8_t* out, uint32_t outCap) {
if (g_keyDescVer == KEY_DESC_VER_RC4) {
KernelLogStream(WARNING, "WiFi")
<< "AP used RC4 key wrapping (WPA1/TKIP), which is not supported";
return 0;
}
if (inLen < 24 || (inLen % 8) != 0 || inLen - 8 > outCap) return 0;
if (!Crypto::AesKeyUnwrap(Kek(), g_kekLen, in, inLen, out)) {
KernelLogStream(WARNING, "WiFi")
<< "EAPOL key data failed its integrity check (wrong passphrase?)";
return 0;
}
return inLen - 8;
}
// Walk the KDE list looking for a GTK (00-0F-AC data type 1).
static bool FindGtk(const uint8_t* data, uint32_t len,
const uint8_t** gtk, uint32_t* gtkLen, uint8_t* keyIdx) {
uint32_t off = 0;
while (off + 2 <= len) {
uint8_t id = data[off];
uint8_t elen = data[off + 1];
if (elen == 0 || off + 2 + elen > len) break;
const uint8_t* body = data + off + 2;
if (id == IEEE80211_ELEMID_VENDOR && elen >= 6 && IsRsnSuite(body)
&& body[3] == 1) {
// GTK KDE: [OUI 3][type 1][keyid+tx 1][reserved 1][GTK ...]
*keyIdx = (uint8_t)(body[4] & 0x03);
*gtk = body + 6;
*gtkLen = (uint32_t)(elen - 6);
return true;
}
off += 2 + elen;
}
return false;
}
// =========================================================================
// Inbound handling
// =========================================================================
static void Fail(const char* why) {
KernelLogStream(WARNING, "WiFi") << "WPA handshake failed: " << why;
g_state = WpaState::Failed;
Crypto::SecureZero(g_ptk, sizeof(g_ptk));
}
static void HandleMsg1(const uint8_t* frame, uint32_t len) {
(void)len;
auto* k = (const EapolKey*)frame;
memcpy(g_anonce, k->nonce, 32);
memcpy(g_replay, k->replay, 8);
g_haveReplay = true;
DerivePtk();
if (!SendMsg2()) {
Fail("could not transmit message 2");
return;
}
g_state = WpaState::WaitMsg3;
KernelLogStream(INFO, "WiFi") << "Handshake message 1 received; sent message 2";
}
static void HandleMsg3(const uint8_t* frame, uint32_t len) {
auto* k = (const EapolKey*)frame;
KernelLogStream(INFO, "WiFi") << "Handshake message 3 received";
// The ANonce must not have changed; if it has, the AP restarted the
// exchange and our PTK is stale.
if (memcmp(k->nonce, g_anonce, 32) != 0) {
Fail("the AP changed its nonce mid-handshake");
return;
}
memcpy(g_replay, k->replay, 8);
uint16_t keyInfo = Get16Be(k->keyInfo);
uint32_t keyDataLen = Get16Be(k->keyDataLen);
if (sizeof(EapolKey) + keyDataLen > len) {
Fail("truncated key data");
return;
}
const uint8_t* keyData = frame + sizeof(EapolKey);
uint8_t plain[MAX_KEY_DATA];
uint32_t plainLen = 0;
if (keyInfo & KEY_INFO_ENCRYPTED) {
plainLen = DecryptKeyData(keyData, keyDataLen, plain, sizeof(plain));
if (!plainLen) {
Fail("could not decrypt the group key");
return;
}
} else if (keyDataLen <= sizeof(plain)) {
memcpy(plain, keyData, keyDataLen);
plainLen = keyDataLen;
}
// Message 4 goes out before the keys are installed: the AP is still
// sending in the clear until it sees it.
if (!SendMsg4()) {
Fail("could not transmit message 4");
return;
}
if (!WpaInstallPtk(Tk(), g_tkLen, g_cfg.PairwiseCipher)) {
Fail("the firmware rejected the pairwise key");
return;
}
const uint8_t* gtk = nullptr;
uint32_t gtkLen = 0;
uint8_t keyIdx = 0;
if (FindGtk(plain, plainLen, &gtk, &gtkLen, &keyIdx)) {
if (!WpaInstallGtk(gtk, gtkLen, keyIdx, g_cfg.GroupCipher, k->rsc))
KernelLogStream(WARNING, "WiFi")
<< "Group key install failed; broadcast traffic will not be received";
} else {
KernelLogStream(WARNING, "WiFi")
<< "No group key in message 3; broadcast traffic will not be received";
}
g_state = WpaState::Complete;
g_lastTxLen = 0;
KernelLogStream(OK, "WiFi") << "WPA handshake complete; link is encrypted";
}
// Periodic GTK rekey initiated by the AP (a 2-way exchange).
static void HandleGroupKey(const uint8_t* frame, uint32_t len) {
auto* k = (const EapolKey*)frame;
memcpy(g_replay, k->replay, 8);
uint16_t keyInfo = Get16Be(k->keyInfo);
uint32_t keyDataLen = Get16Be(k->keyDataLen);
if (sizeof(EapolKey) + keyDataLen > len) return;
const uint8_t* keyData = frame + sizeof(EapolKey);
uint8_t plain[MAX_KEY_DATA];
uint32_t plainLen = 0;
if (keyInfo & KEY_INFO_ENCRYPTED) {
plainLen = DecryptKeyData(keyData, keyDataLen, plain, sizeof(plain));
if (!plainLen) return;
} else if (keyDataLen <= sizeof(plain)) {
memcpy(plain, keyData, keyDataLen);
plainLen = keyDataLen;
}
const uint8_t* gtk = nullptr;
uint32_t gtkLen = 0;
uint8_t keyIdx = 0;
if (FindGtk(plain, plainLen, &gtk, &gtkLen, &keyIdx))
WpaInstallGtk(gtk, gtkLen, keyIdx, g_cfg.GroupCipher, k->rsc);
SendGroupAck();
KernelLogStream(INFO, "WiFi") << "Group key rekeyed";
}
bool WpaOnEapol(const uint8_t* data, uint32_t len) {
if (g_state == WpaState::Idle || g_state == WpaState::Failed) return false;
if (len < sizeof(EapolKey)) return false;
auto* k = (const EapolKey*)data;
if (k->type != EAPOL_TYPE_KEY) return false;
if (k->descType != EAPOL_KEY_DESC_RSN && k->descType != EAPOL_KEY_DESC_WPA)
return false;
// Trust the frame's own length field over the (possibly padded) buffer
// the driver handed us.
uint32_t declared = (uint32_t)Get16Be(k->length) + 4;
if (declared < sizeof(EapolKey) || declared > len) return false;
len = declared;
g_eapolVersion = k->version > 3 ? 2 : k->version;
g_descType = k->descType;
uint16_t keyInfo = Get16Be(k->keyInfo);
uint8_t ver = (uint8_t)(keyInfo & KEY_INFO_VERSION_MASK);
if (keyInfo & KEY_INFO_REQUEST) return false; // STA->AP direction
if (ver == KEY_DESC_VER_RC4) {
Fail("the AP asked for RC4/TKIP key wrapping, which is not supported");
return true;
}
if (ver != KEY_DESC_VER_AES && ver != KEY_DESC_VER_AES_CMAC) {
Fail("unknown EAPOL key descriptor version");
return true;
}
bool pairwise = (keyInfo & KEY_INFO_KEY_TYPE) != 0;
bool hasMic = (keyInfo & KEY_INFO_MIC) != 0;
bool hasAck = (keyInfo & KEY_INFO_ACK) != 0;
if (keyInfo & KEY_INFO_ERROR) {
Fail("the AP reported a MIC failure");
return true;
}
// Message 1 is the only frame that arrives before a PTK exists, so it
// is also the only one whose descriptor version we can adopt.
if (pairwise && hasAck && !hasMic) {
if (g_state != WpaState::WaitMsg1 && g_state != WpaState::WaitMsg3) {
// The AP restarted the handshake; take it from the top.
GenNonce(g_snonce);
}
g_keyDescVer = ver;
HandleMsg1(data, len);
return true;
}
// Everything else is MIC-protected, so the PTK has to exist first.
if (!hasMic) return false;
if (g_state == WpaState::WaitMsg1) return false;
if (!VerifyMic(data, len)) {
KernelLogStream(WARNING, "WiFi")
<< "Discarding EAPOL frame with a bad MIC";
return true;
}
if (pairwise && hasAck) {
if (g_state == WpaState::Complete) {
// Our message 4 did not reach the AP and it retried message 3.
// The keys are already in place, so just answer again.
memcpy(g_replay, k->replay, 8);
SendMsg4();
} else {
HandleMsg3(data, len);
}
} else if (!pairwise && hasAck) {
HandleGroupKey(data, len);
}
return true;
}
// =========================================================================
// Retransmission / timeout
// =========================================================================
// Unsigned deadline test that survives a `since` newer than `nowMs`. The
// caller samples the clock once per service pass while g_lastTxMs is
// stamped the moment a frame goes out, so the two can cross; a plain
// subtraction then wraps and expires every timer at once.
static bool Elapsed(uint64_t nowMs, uint64_t since, uint64_t ms) {
return nowMs > since && nowMs - since > ms;
}
void WpaService(uint64_t nowMs) {
if (g_state != WpaState::WaitMsg1 && g_state != WpaState::WaitMsg3) return;
if (g_startMs == 0) g_startMs = nowMs;
if (Elapsed(nowMs, g_startMs, HANDSHAKE_TIMEOUT_MS)) {
Fail("the AP stopped responding");
return;
}
// Only our own messages are worth retrying; while waiting for msg 1
// there is nothing to resend, the AP drives that.
if (g_state == WpaState::WaitMsg3 && g_lastTxLen
&& Elapsed(nowMs, g_lastTxMs, RETRY_INTERVAL_MS)) {
if (g_retries >= MAX_RETRIES) {
Fail("no response to message 2");
return;
}
g_retries++;
g_lastTxMs = nowMs;
WpaTxEapol(g_lastTx, g_lastTxLen);
}
}
}
+82
View File
@@ -0,0 +1,82 @@
/*
* Wpa.hpp
* WPA2/WPA3-PSK supplicant: PMK derivation and the EAPOL-Key 4-way
* handshake that unlocks the link after association.
*
* The supplicant lives in the kernel because the handshake sits between
* association and the first IP packet: nothing above the driver can send or
* receive until the pairwise key is installed in the firmware. It drives
* the exchange but does not touch the hardware itself -- transmitting and
* key installation are provided by the MLME through the three hooks at the
* bottom of this header.
*
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Drivers::Net::Wifi {
enum class WpaState : uint8_t {
Idle = 0,
WaitMsg1, // associated, waiting for the AP to start the exchange
WaitMsg3, // msg 2 sent, waiting for the GTK
Complete, // keys installed, link is usable
Failed,
};
struct WpaConfig {
uint8_t OwnMac[6];
uint8_t Bssid[6];
uint8_t Ssid[32];
uint8_t SsidLen;
char Passphrase[64];
uint8_t PassLen;
uint8_t Akm; // RSN_AKM_*
uint8_t PairwiseCipher; // RSN_CIPHER_*
uint8_t GroupCipher;
bool Mfp; // management frame protection negotiated
};
// Derive the PMK and arm the handshake. Returns false when the
// configuration names a cipher or AKM this supplicant cannot do.
bool WpaStart(const WpaConfig& cfg);
void WpaReset();
// Feed the 802.1X payload of an inbound EAPOL frame (everything after the
// LLC/SNAP header). Returns true when the frame was consumed.
bool WpaOnEapol(const uint8_t* data, uint32_t len);
// Re-send the last outbound message if the AP has gone quiet, and fail the
// handshake once it has been silent for too long. Called from the idle
// loop; `nowMs` is a monotonic millisecond clock.
void WpaService(uint64_t nowMs);
WpaState WpaGetState();
bool WpaIsComplete();
// Build the RSN information element advertising what WpaStart() was
// configured with. Returns the number of bytes written, 0 on error.
uint32_t WpaBuildRsnIe(uint8_t* out, uint32_t cap);
// Pick the pairwise/group cipher and AKM out of an AP's RSN IE. `ie`
// points at the element body (after id/len). Returns false when nothing
// in the IE is supported.
bool WpaParseApRsn(const uint8_t* ie, uint32_t len, WpaConfig& cfg);
// -------------------------------------------------------------------------
// Hooks implemented by the MLME (IwxConnect.cpp)
// -------------------------------------------------------------------------
// Transmit an EAPOL frame body (802.1X header included) to the AP.
bool WpaTxEapol(const uint8_t* body, uint32_t len);
// Install the pairwise temporal key. `cipher` is an RSN_CIPHER_* value.
bool WpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher);
// Install a group temporal key at `keyIdx`. `rsc` is the EAPOL key RSC
// field: 8 bytes, of which the low 6 are the AP's packet number.
bool WpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
uint8_t cipher, const uint8_t* rsc);
}
+700
View File
@@ -0,0 +1,700 @@
/*
* Crypto.cpp
* SHA-1, SHA-256, HMAC, PBKDF2, AES and AES-CMAC for the Wi-Fi supplicant.
* See Crypto.hpp for why these live in the kernel.
* Copyright (c) 2026 Daniel Hammer
*/
#include "Crypto.hpp"
#include <Libraries/Memory.hpp>
namespace Kt::Crypto {
// =========================================================================
// Small helpers
// =========================================================================
static inline uint32_t Rol32(uint32_t v, int n) {
return (v << n) | (v >> (32 - n));
}
static inline uint32_t Ror32(uint32_t v, int n) {
return (v >> n) | (v << (32 - n));
}
static inline uint32_t LoadBe32(const uint8_t* p) {
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16)
| ((uint32_t)p[2] << 8) | (uint32_t)p[3];
}
static inline void StoreBe32(uint8_t* p, uint32_t v) {
p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16);
p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)v;
}
static inline void StoreBe64(uint8_t* p, uint64_t v) {
StoreBe32(p, (uint32_t)(v >> 32));
StoreBe32(p + 4, (uint32_t)v);
}
void SecureZero(void* p, size_t len) {
volatile uint8_t* q = (volatile uint8_t*)p;
while (len--) *q++ = 0;
}
bool SecureEqual(const void* a, const void* b, size_t len) {
const uint8_t* x = (const uint8_t*)a;
const uint8_t* y = (const uint8_t*)b;
uint8_t diff = 0;
for (size_t i = 0; i < len; i++) diff |= (uint8_t)(x[i] ^ y[i]);
return diff == 0;
}
// =========================================================================
// SHA-1
// =========================================================================
static void Sha1Block(uint32_t* st, const uint8_t* block) {
uint32_t w[80];
for (int i = 0; i < 16; i++) w[i] = LoadBe32(block + i * 4);
for (int i = 16; i < 80; i++)
w[i] = Rol32(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
uint32_t a = st[0], b = st[1], c = st[2], d = st[3], e = st[4];
for (int i = 0; i < 80; i++) {
uint32_t f, k;
if (i < 20) { f = (b & c) | (~b & d); k = 0x5A827999; }
else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; }
else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; }
else { f = b ^ c ^ d; k = 0xCA62C1D6; }
uint32_t t = Rol32(a, 5) + f + e + k + w[i];
e = d; d = c; c = Rol32(b, 30); b = a; a = t;
}
st[0] += a; st[1] += b; st[2] += c; st[3] += d; st[4] += e;
}
void Sha1Init(Sha1Ctx& ctx) {
ctx.State[0] = 0x67452301; ctx.State[1] = 0xEFCDAB89;
ctx.State[2] = 0x98BADCFE; ctx.State[3] = 0x10325476;
ctx.State[4] = 0xC3D2E1F0;
ctx.Count = 0;
ctx.Partial = 0;
}
void Sha1Update(Sha1Ctx& ctx, const void* data, size_t len) {
const uint8_t* p = (const uint8_t*)data;
ctx.Count += len;
if (ctx.Partial) {
uint32_t need = SHA1_BLOCK_SIZE - ctx.Partial;
uint32_t take = (len < need) ? (uint32_t)len : need;
memcpy(ctx.Buffer + ctx.Partial, p, take);
ctx.Partial += take;
p += take;
len -= take;
if (ctx.Partial < SHA1_BLOCK_SIZE) return;
Sha1Block(ctx.State, ctx.Buffer);
ctx.Partial = 0;
}
while (len >= SHA1_BLOCK_SIZE) {
Sha1Block(ctx.State, p);
p += SHA1_BLOCK_SIZE;
len -= SHA1_BLOCK_SIZE;
}
if (len) {
memcpy(ctx.Buffer, p, len);
ctx.Partial = (uint32_t)len;
}
}
void Sha1Final(Sha1Ctx& ctx, uint8_t out[SHA1_DIGEST_SIZE]) {
uint64_t bits = ctx.Count * 8;
ctx.Buffer[ctx.Partial++] = 0x80;
if (ctx.Partial > SHA1_BLOCK_SIZE - 8) {
memset(ctx.Buffer + ctx.Partial, 0, SHA1_BLOCK_SIZE - ctx.Partial);
Sha1Block(ctx.State, ctx.Buffer);
ctx.Partial = 0;
}
memset(ctx.Buffer + ctx.Partial, 0, SHA1_BLOCK_SIZE - 8 - ctx.Partial);
StoreBe64(ctx.Buffer + SHA1_BLOCK_SIZE - 8, bits);
Sha1Block(ctx.State, ctx.Buffer);
for (int i = 0; i < 5; i++) StoreBe32(out + i * 4, ctx.State[i]);
SecureZero(ctx.Buffer, sizeof(ctx.Buffer));
}
void Sha1(const void* data, size_t len, uint8_t out[SHA1_DIGEST_SIZE]) {
Sha1Ctx ctx;
Sha1Init(ctx);
Sha1Update(ctx, data, len);
Sha1Final(ctx, out);
}
// =========================================================================
// SHA-256
// =========================================================================
static const uint32_t kSha256K[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
};
static void Sha256Block(uint32_t* st, const uint8_t* block) {
uint32_t w[64];
for (int i = 0; i < 16; i++) w[i] = LoadBe32(block + i * 4);
for (int i = 16; i < 64; i++) {
uint32_t s0 = Ror32(w[i - 15], 7) ^ Ror32(w[i - 15], 18) ^ (w[i - 15] >> 3);
uint32_t s1 = Ror32(w[i - 2], 17) ^ Ror32(w[i - 2], 19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
uint32_t a = st[0], b = st[1], c = st[2], d = st[3];
uint32_t e = st[4], f = st[5], g = st[6], h = st[7];
for (int i = 0; i < 64; i++) {
uint32_t S1 = Ror32(e, 6) ^ Ror32(e, 11) ^ Ror32(e, 25);
uint32_t ch = (e & f) ^ (~e & g);
uint32_t t1 = h + S1 + ch + kSha256K[i] + w[i];
uint32_t S0 = Ror32(a, 2) ^ Ror32(a, 13) ^ Ror32(a, 22);
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
uint32_t t2 = S0 + maj;
h = g; g = f; f = e; e = d + t1;
d = c; c = b; b = a; a = t1 + t2;
}
st[0] += a; st[1] += b; st[2] += c; st[3] += d;
st[4] += e; st[5] += f; st[6] += g; st[7] += h;
}
void Sha256Init(Sha256Ctx& ctx) {
ctx.State[0] = 0x6a09e667; ctx.State[1] = 0xbb67ae85;
ctx.State[2] = 0x3c6ef372; ctx.State[3] = 0xa54ff53a;
ctx.State[4] = 0x510e527f; ctx.State[5] = 0x9b05688c;
ctx.State[6] = 0x1f83d9ab; ctx.State[7] = 0x5be0cd19;
ctx.Count = 0;
ctx.Partial = 0;
}
void Sha256Update(Sha256Ctx& ctx, const void* data, size_t len) {
const uint8_t* p = (const uint8_t*)data;
ctx.Count += len;
if (ctx.Partial) {
uint32_t need = SHA256_BLOCK_SIZE - ctx.Partial;
uint32_t take = (len < need) ? (uint32_t)len : need;
memcpy(ctx.Buffer + ctx.Partial, p, take);
ctx.Partial += take;
p += take;
len -= take;
if (ctx.Partial < SHA256_BLOCK_SIZE) return;
Sha256Block(ctx.State, ctx.Buffer);
ctx.Partial = 0;
}
while (len >= SHA256_BLOCK_SIZE) {
Sha256Block(ctx.State, p);
p += SHA256_BLOCK_SIZE;
len -= SHA256_BLOCK_SIZE;
}
if (len) {
memcpy(ctx.Buffer, p, len);
ctx.Partial = (uint32_t)len;
}
}
void Sha256Final(Sha256Ctx& ctx, uint8_t out[SHA256_DIGEST_SIZE]) {
uint64_t bits = ctx.Count * 8;
ctx.Buffer[ctx.Partial++] = 0x80;
if (ctx.Partial > SHA256_BLOCK_SIZE - 8) {
memset(ctx.Buffer + ctx.Partial, 0, SHA256_BLOCK_SIZE - ctx.Partial);
Sha256Block(ctx.State, ctx.Buffer);
ctx.Partial = 0;
}
memset(ctx.Buffer + ctx.Partial, 0, SHA256_BLOCK_SIZE - 8 - ctx.Partial);
StoreBe64(ctx.Buffer + SHA256_BLOCK_SIZE - 8, bits);
Sha256Block(ctx.State, ctx.Buffer);
for (int i = 0; i < 8; i++) StoreBe32(out + i * 4, ctx.State[i]);
SecureZero(ctx.Buffer, sizeof(ctx.Buffer));
}
void Sha256(const void* data, size_t len, uint8_t out[SHA256_DIGEST_SIZE]) {
Sha256Ctx ctx;
Sha256Init(ctx);
Sha256Update(ctx, data, len);
Sha256Final(ctx, out);
}
// =========================================================================
// HMAC
// =========================================================================
void HmacSha1(const uint8_t* key, size_t keyLen,
const uint8_t* const* parts, const size_t* lens, int count,
uint8_t out[SHA1_DIGEST_SIZE]) {
uint8_t k[SHA1_BLOCK_SIZE] = {};
if (keyLen > SHA1_BLOCK_SIZE) {
Sha1(key, keyLen, k);
} else {
memcpy(k, key, keyLen);
}
uint8_t pad[SHA1_BLOCK_SIZE];
Sha1Ctx ctx;
for (int i = 0; i < (int)SHA1_BLOCK_SIZE; i++) pad[i] = (uint8_t)(k[i] ^ 0x36);
Sha1Init(ctx);
Sha1Update(ctx, pad, SHA1_BLOCK_SIZE);
for (int i = 0; i < count; i++) Sha1Update(ctx, parts[i], lens[i]);
uint8_t inner[SHA1_DIGEST_SIZE];
Sha1Final(ctx, inner);
for (int i = 0; i < (int)SHA1_BLOCK_SIZE; i++) pad[i] = (uint8_t)(k[i] ^ 0x5c);
Sha1Init(ctx);
Sha1Update(ctx, pad, SHA1_BLOCK_SIZE);
Sha1Update(ctx, inner, SHA1_DIGEST_SIZE);
Sha1Final(ctx, out);
SecureZero(k, sizeof(k));
SecureZero(pad, sizeof(pad));
SecureZero(inner, sizeof(inner));
}
void HmacSha1(const uint8_t* key, size_t keyLen,
const void* data, size_t len, uint8_t out[SHA1_DIGEST_SIZE]) {
const uint8_t* p = (const uint8_t*)data;
HmacSha1(key, keyLen, &p, &len, 1, out);
}
void HmacSha256(const uint8_t* key, size_t keyLen,
const uint8_t* const* parts, const size_t* lens, int count,
uint8_t out[SHA256_DIGEST_SIZE]) {
uint8_t k[SHA256_BLOCK_SIZE] = {};
if (keyLen > SHA256_BLOCK_SIZE) {
Sha256(key, keyLen, k);
} else {
memcpy(k, key, keyLen);
}
uint8_t pad[SHA256_BLOCK_SIZE];
Sha256Ctx ctx;
for (int i = 0; i < (int)SHA256_BLOCK_SIZE; i++) pad[i] = (uint8_t)(k[i] ^ 0x36);
Sha256Init(ctx);
Sha256Update(ctx, pad, SHA256_BLOCK_SIZE);
for (int i = 0; i < count; i++) Sha256Update(ctx, parts[i], lens[i]);
uint8_t inner[SHA256_DIGEST_SIZE];
Sha256Final(ctx, inner);
for (int i = 0; i < (int)SHA256_BLOCK_SIZE; i++) pad[i] = (uint8_t)(k[i] ^ 0x5c);
Sha256Init(ctx);
Sha256Update(ctx, pad, SHA256_BLOCK_SIZE);
Sha256Update(ctx, inner, SHA256_DIGEST_SIZE);
Sha256Final(ctx, out);
SecureZero(k, sizeof(k));
SecureZero(pad, sizeof(pad));
SecureZero(inner, sizeof(inner));
}
void HmacSha256(const uint8_t* key, size_t keyLen,
const void* data, size_t len, uint8_t out[SHA256_DIGEST_SIZE]) {
const uint8_t* p = (const uint8_t*)data;
HmacSha256(key, keyLen, &p, &len, 1, out);
}
// =========================================================================
// PBKDF2-HMAC-SHA1
// =========================================================================
void Pbkdf2Sha1(const char* password, size_t passLen,
const uint8_t* salt, size_t saltLen,
uint32_t iterations, uint8_t* out, size_t outLen) {
const uint8_t* pw = (const uint8_t*)password;
uint32_t block = 1;
while (outLen > 0) {
uint8_t counter[4] = {
(uint8_t)(block >> 24), (uint8_t)(block >> 16),
(uint8_t)(block >> 8), (uint8_t)block
};
const uint8_t* parts[2] = { salt, counter };
size_t lens[2] = { saltLen, 4 };
uint8_t u[SHA1_DIGEST_SIZE];
uint8_t acc[SHA1_DIGEST_SIZE];
HmacSha1(pw, passLen, parts, lens, 2, u);
memcpy(acc, u, SHA1_DIGEST_SIZE);
for (uint32_t i = 1; i < iterations; i++) {
HmacSha1(pw, passLen, u, SHA1_DIGEST_SIZE, u);
for (int j = 0; j < (int)SHA1_DIGEST_SIZE; j++) acc[j] ^= u[j];
}
size_t take = outLen < SHA1_DIGEST_SIZE ? outLen : SHA1_DIGEST_SIZE;
memcpy(out, acc, take);
out += take;
outLen -= take;
block++;
SecureZero(u, sizeof(u));
SecureZero(acc, sizeof(acc));
}
}
// =========================================================================
// AES
// =========================================================================
static const uint8_t kSbox[256] = {
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,
};
static const uint8_t kRsbox[256] = {
0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d,
};
// Round constants; indexed by (i / Nk), which starts at 1.
static const uint8_t kRcon[11] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
};
static inline uint8_t Xtime(uint8_t x) {
return (uint8_t)((x << 1) ^ (((x >> 7) & 1) * 0x1b));
}
// Galois-field multiply, used only by the inverse mix-columns step.
static uint8_t GfMul(uint8_t x, uint8_t y) {
uint8_t r = 0;
for (int i = 0; i < 8; i++) {
if (y & 1) r ^= x;
uint8_t hi = (uint8_t)(x & 0x80);
x <<= 1;
if (hi) x ^= 0x1b;
y >>= 1;
}
return r;
}
bool AesInit(AesCtx& ctx, const uint8_t* key, size_t keyLen) {
int nk, nr;
if (keyLen == 16) { nk = 4; nr = 10; }
else if (keyLen == 32) { nk = 8; nr = 14; }
else return false;
ctx.Rounds = nr;
uint8_t* rk = ctx.RoundKey;
memcpy(rk, key, keyLen);
for (int i = nk; i < 4 * (nr + 1); i++) {
uint8_t t[4];
int k = (i - 1) * 4;
t[0] = rk[k + 0]; t[1] = rk[k + 1]; t[2] = rk[k + 2]; t[3] = rk[k + 3];
if (i % nk == 0) {
uint8_t tmp = t[0];
t[0] = kSbox[t[1]]; t[1] = kSbox[t[2]];
t[2] = kSbox[t[3]]; t[3] = kSbox[tmp];
t[0] ^= kRcon[i / nk];
} else if (nk > 6 && i % nk == 4) {
t[0] = kSbox[t[0]]; t[1] = kSbox[t[1]];
t[2] = kSbox[t[2]]; t[3] = kSbox[t[3]];
}
int j = i * 4;
k = (i - nk) * 4;
rk[j + 0] = (uint8_t)(rk[k + 0] ^ t[0]);
rk[j + 1] = (uint8_t)(rk[k + 1] ^ t[1]);
rk[j + 2] = (uint8_t)(rk[k + 2] ^ t[2]);
rk[j + 3] = (uint8_t)(rk[k + 3] ^ t[3]);
}
return true;
}
// The state is column-major: s[4 * col + row], matching the AES input map.
static inline void AddRoundKey(uint8_t* s, const uint8_t* rk, int round) {
const uint8_t* k = rk + round * 16;
for (int i = 0; i < 16; i++) s[i] ^= k[i];
}
static void SubShift(uint8_t* s) {
for (int i = 0; i < 16; i++) s[i] = kSbox[s[i]];
uint8_t t;
// Row 1 left by one.
t = s[1]; s[1] = s[5]; s[5] = s[9]; s[9] = s[13]; s[13] = t;
// Row 2 left by two.
t = s[2]; s[2] = s[10]; s[10] = t;
t = s[6]; s[6] = s[14]; s[14] = t;
// Row 3 left by three (equivalently right by one).
t = s[15]; s[15] = s[11]; s[11] = s[7]; s[7] = s[3]; s[3] = t;
}
static void InvShiftSub(uint8_t* s) {
uint8_t t;
// Row 1 right by one.
t = s[13]; s[13] = s[9]; s[9] = s[5]; s[5] = s[1]; s[1] = t;
// Row 2 right by two.
t = s[2]; s[2] = s[10]; s[10] = t;
t = s[6]; s[6] = s[14]; s[14] = t;
// Row 3 right by three.
t = s[3]; s[3] = s[7]; s[7] = s[11]; s[11] = s[15]; s[15] = t;
for (int i = 0; i < 16; i++) s[i] = kRsbox[s[i]];
}
static void MixColumns(uint8_t* s) {
for (int c = 0; c < 4; c++) {
uint8_t* p = s + c * 4;
uint8_t a0 = p[0];
uint8_t all = (uint8_t)(p[0] ^ p[1] ^ p[2] ^ p[3]);
p[0] ^= (uint8_t)(Xtime((uint8_t)(p[0] ^ p[1])) ^ all);
p[1] ^= (uint8_t)(Xtime((uint8_t)(p[1] ^ p[2])) ^ all);
p[2] ^= (uint8_t)(Xtime((uint8_t)(p[2] ^ p[3])) ^ all);
p[3] ^= (uint8_t)(Xtime((uint8_t)(p[3] ^ a0)) ^ all);
}
}
static void InvMixColumns(uint8_t* s) {
for (int c = 0; c < 4; c++) {
uint8_t* p = s + c * 4;
uint8_t a = p[0], b = p[1], d = p[2], e = p[3];
p[0] = (uint8_t)(GfMul(a, 0x0e) ^ GfMul(b, 0x0b) ^ GfMul(d, 0x0d) ^ GfMul(e, 0x09));
p[1] = (uint8_t)(GfMul(a, 0x09) ^ GfMul(b, 0x0e) ^ GfMul(d, 0x0b) ^ GfMul(e, 0x0d));
p[2] = (uint8_t)(GfMul(a, 0x0d) ^ GfMul(b, 0x09) ^ GfMul(d, 0x0e) ^ GfMul(e, 0x0b));
p[3] = (uint8_t)(GfMul(a, 0x0b) ^ GfMul(b, 0x0d) ^ GfMul(d, 0x09) ^ GfMul(e, 0x0e));
}
}
void AesEncryptBlock(const AesCtx& ctx, const uint8_t in[16], uint8_t out[16]) {
uint8_t s[16];
memcpy(s, in, 16);
AddRoundKey(s, ctx.RoundKey, 0);
for (int round = 1; round < ctx.Rounds; round++) {
SubShift(s);
MixColumns(s);
AddRoundKey(s, ctx.RoundKey, round);
}
SubShift(s);
AddRoundKey(s, ctx.RoundKey, ctx.Rounds);
memcpy(out, s, 16);
}
void AesDecryptBlock(const AesCtx& ctx, const uint8_t in[16], uint8_t out[16]) {
uint8_t s[16];
memcpy(s, in, 16);
AddRoundKey(s, ctx.RoundKey, ctx.Rounds);
for (int round = ctx.Rounds - 1; round > 0; round--) {
InvShiftSub(s);
AddRoundKey(s, ctx.RoundKey, round);
InvMixColumns(s);
}
InvShiftSub(s);
AddRoundKey(s, ctx.RoundKey, 0);
memcpy(out, s, 16);
}
// =========================================================================
// RFC 3394 AES key wrap
// =========================================================================
static const uint8_t kKeyWrapIv[8] = {
0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6
};
bool AesKeyUnwrap(const uint8_t* kek, size_t kekLen,
const uint8_t* in, size_t inLen, uint8_t* out) {
if (inLen < 24 || (inLen % 8) != 0) return false;
size_t n = inLen / 8 - 1;
AesCtx ctx;
if (!AesInit(ctx, kek, kekLen)) return false;
uint8_t a[8];
memcpy(a, in, 8);
memcpy(out, in + 8, n * 8);
uint8_t block[16];
for (int j = 5; j >= 0; j--) {
for (size_t i = n; i >= 1; i--) {
uint64_t t = (uint64_t)n * (uint64_t)j + i;
memcpy(block, a, 8);
// A ^= t, big-endian over the full 8-byte word.
for (int b = 0; b < 8; b++)
block[7 - b] ^= (uint8_t)(t >> (8 * b));
memcpy(block + 8, out + (i - 1) * 8, 8);
AesDecryptBlock(ctx, block, block);
memcpy(a, block, 8);
memcpy(out + (i - 1) * 8, block + 8, 8);
}
}
SecureZero(block, sizeof(block));
SecureZero(&ctx, sizeof(ctx));
return SecureEqual(a, kKeyWrapIv, 8);
}
bool AesKeyWrap(const uint8_t* kek, size_t kekLen,
const uint8_t* in, size_t inLen, uint8_t* out) {
if (inLen < 16 || (inLen % 8) != 0) return false;
size_t n = inLen / 8;
AesCtx ctx;
if (!AesInit(ctx, kek, kekLen)) return false;
uint8_t a[8];
memcpy(a, kKeyWrapIv, 8);
memcpy(out + 8, in, inLen);
uint8_t block[16];
for (int j = 0; j < 6; j++) {
for (size_t i = 1; i <= n; i++) {
memcpy(block, a, 8);
memcpy(block + 8, out + i * 8, 8);
AesEncryptBlock(ctx, block, block);
uint64_t t = (uint64_t)n * (uint64_t)j + i;
memcpy(a, block, 8);
for (int b = 0; b < 8; b++)
a[7 - b] ^= (uint8_t)(t >> (8 * b));
memcpy(out + i * 8, block + 8, 8);
}
}
memcpy(out, a, 8);
SecureZero(block, sizeof(block));
SecureZero(&ctx, sizeof(ctx));
return true;
}
// =========================================================================
// AES-CMAC (RFC 4493)
// =========================================================================
static void CmacShiftLeft(const uint8_t in[16], uint8_t out[16]) {
uint8_t carry = 0;
for (int i = 15; i >= 0; i--) {
uint8_t v = in[i];
out[i] = (uint8_t)((v << 1) | carry);
carry = (uint8_t)((v >> 7) & 1);
}
}
void AesCmac(const uint8_t* key, size_t keyLen,
const uint8_t* const* parts, const size_t* lens, int count,
uint8_t out[16]) {
AesCtx ctx;
if (!AesInit(ctx, key, keyLen)) {
memset(out, 0, 16);
return;
}
// Subkey generation.
uint8_t zero[16] = {};
uint8_t l[16], k1[16], k2[16];
AesEncryptBlock(ctx, zero, l);
CmacShiftLeft(l, k1);
if (l[0] & 0x80) k1[15] ^= 0x87;
CmacShiftLeft(k1, k2);
if (k1[0] & 0x80) k2[15] ^= 0x87;
size_t total = 0;
for (int i = 0; i < count; i++) total += lens[i];
uint8_t x[16] = {};
uint8_t block[16];
uint32_t fill = 0;
// Stream the parts through, holding back the final block so it can be
// padded and XORed with the right subkey.
size_t consumed = 0;
for (int i = 0; i < count; i++) {
const uint8_t* p = parts[i];
size_t n = lens[i];
while (n) {
uint32_t take = 16 - fill;
if (take > n) take = (uint32_t)n;
memcpy(block + fill, p, take);
fill += take;
p += take;
n -= take;
consumed += take;
if (fill == 16 && consumed < total) {
for (int b = 0; b < 16; b++) x[b] ^= block[b];
AesEncryptBlock(ctx, x, x);
fill = 0;
}
}
}
if (fill == 16 && total != 0) {
for (int b = 0; b < 16; b++) block[b] ^= k1[b];
} else {
block[fill] = 0x80;
for (uint32_t b = fill + 1; b < 16; b++) block[b] = 0;
for (int b = 0; b < 16; b++) block[b] ^= k2[b];
}
for (int b = 0; b < 16; b++) x[b] ^= block[b];
AesEncryptBlock(ctx, x, out);
SecureZero(l, sizeof(l));
SecureZero(k1, sizeof(k1));
SecureZero(k2, sizeof(k2));
SecureZero(block, sizeof(block));
SecureZero(&ctx, sizeof(ctx));
}
}
+132
View File
@@ -0,0 +1,132 @@
/*
* Crypto.hpp
* Minimal kernel-side crypto primitives.
*
* These exist for the Wi-Fi WPA2/WPA3 supplicant, which has to run inside the
* kernel (the 4-way handshake is bound to the driver's TX/RX path and has to
* complete before any IP traffic can flow). BearSSL lives in userspace and
* is not linkable here, so the handful of primitives the handshake needs are
* implemented directly:
*
* SHA-1 / SHA-256 digest + HMAC EAPOL MIC, PRF, PBKDF2
* PBKDF2-HMAC-SHA1 WPA passphrase -> 256-bit PSK
* AES-128/256 key unwrap, CMAC
* AES key unwrap (RFC 3394) encrypted EAPOL key data
* AES-CMAC key-descriptor version 3 MIC
*
* Nothing here is constant-time hardened beyond avoiding secret-dependent
* branches in the comparison helper; it is not a general-purpose crypto
* library and should not be used as one.
*
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <cstddef>
namespace Kt::Crypto {
// =========================================================================
// SHA-1
// =========================================================================
constexpr uint32_t SHA1_DIGEST_SIZE = 20;
constexpr uint32_t SHA1_BLOCK_SIZE = 64;
struct Sha1Ctx {
uint32_t State[5];
uint64_t Count; // total bytes fed
uint8_t Buffer[SHA1_BLOCK_SIZE];
uint32_t Partial;
};
void Sha1Init(Sha1Ctx& ctx);
void Sha1Update(Sha1Ctx& ctx, const void* data, size_t len);
void Sha1Final(Sha1Ctx& ctx, uint8_t out[SHA1_DIGEST_SIZE]);
void Sha1(const void* data, size_t len, uint8_t out[SHA1_DIGEST_SIZE]);
// =========================================================================
// SHA-256
// =========================================================================
constexpr uint32_t SHA256_DIGEST_SIZE = 32;
constexpr uint32_t SHA256_BLOCK_SIZE = 64;
struct Sha256Ctx {
uint32_t State[8];
uint64_t Count;
uint8_t Buffer[SHA256_BLOCK_SIZE];
uint32_t Partial;
};
void Sha256Init(Sha256Ctx& ctx);
void Sha256Update(Sha256Ctx& ctx, const void* data, size_t len);
void Sha256Final(Sha256Ctx& ctx, uint8_t out[SHA256_DIGEST_SIZE]);
void Sha256(const void* data, size_t len, uint8_t out[SHA256_DIGEST_SIZE]);
// =========================================================================
// HMAC
// =========================================================================
// Multi-part variants: `parts`/`lens` describe `count` chunks that are
// hashed as one message. The 802.11 PRF feeds four or five chunks per
// iteration, so this avoids staging a concatenation buffer every time.
void HmacSha1(const uint8_t* key, size_t keyLen,
const uint8_t* const* parts, const size_t* lens, int count,
uint8_t out[SHA1_DIGEST_SIZE]);
void HmacSha1(const uint8_t* key, size_t keyLen,
const void* data, size_t len, uint8_t out[SHA1_DIGEST_SIZE]);
void HmacSha256(const uint8_t* key, size_t keyLen,
const uint8_t* const* parts, const size_t* lens, int count,
uint8_t out[SHA256_DIGEST_SIZE]);
void HmacSha256(const uint8_t* key, size_t keyLen,
const void* data, size_t len, uint8_t out[SHA256_DIGEST_SIZE]);
// =========================================================================
// PBKDF2-HMAC-SHA1 (WPA passphrase -> PSK, RFC 2898)
// =========================================================================
void Pbkdf2Sha1(const char* password, size_t passLen,
const uint8_t* salt, size_t saltLen,
uint32_t iterations, uint8_t* out, size_t outLen);
// =========================================================================
// AES (128 and 256 bit keys, single block)
// =========================================================================
struct AesCtx {
uint8_t RoundKey[240]; // 15 round keys, the AES-256 maximum
int Rounds;
};
// keyLen must be 16 or 32 bytes. Returns false otherwise.
bool AesInit(AesCtx& ctx, const uint8_t* key, size_t keyLen);
void AesEncryptBlock(const AesCtx& ctx, const uint8_t in[16], uint8_t out[16]);
void AesDecryptBlock(const AesCtx& ctx, const uint8_t in[16], uint8_t out[16]);
// RFC 3394 AES key wrap / unwrap. `outLen` is `inLen - 8` for unwrap and
// `inLen + 8` for wrap; both operate on multiples of 8 bytes. Unwrap
// returns false when the integrity check value does not match.
bool AesKeyUnwrap(const uint8_t* kek, size_t kekLen,
const uint8_t* in, size_t inLen, uint8_t* out);
bool AesKeyWrap(const uint8_t* kek, size_t kekLen,
const uint8_t* in, size_t inLen, uint8_t* out);
// AES-CMAC (RFC 4493), truncated by the caller as needed.
void AesCmac(const uint8_t* key, size_t keyLen,
const uint8_t* const* parts, const size_t* lens, int count,
uint8_t out[16]);
// =========================================================================
// Helpers
// =========================================================================
// Length-fixed comparison that does not short-circuit on the first
// difference: used for MIC checks so a mismatch position is not observable.
bool SecureEqual(const void* a, const void* b, size_t len);
// Wipe key material. Marked so the compiler cannot elide the stores.
void SecureZero(void* p, size_t len);
}
+5 -3
View File
@@ -15,16 +15,18 @@
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Net/NetIf.hpp>
#include <CppLib/Spinlock.hpp>
using namespace Kt;
namespace Net::Arp {
// Must be the MAC of the interface the frame actually leaves by: an ARP
// reply carrying the wired card's address while the frame goes out over
// Wi-Fi would be answered to a station that is not there.
static const uint8_t* GetActiveNicMac() {
if (Drivers::Net::E1000::IsInitialized())
return Drivers::Net::E1000::GetMacAddress();
return Drivers::Net::E1000E::GetMacAddress();
return NetIf::ActiveMac();
}
// ARP cache entry
+9 -8
View File
@@ -8,8 +8,7 @@
#include <Net/ByteOrder.hpp>
#include <Net/Arp.hpp>
#include <Net/Ipv4.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Net/NetIf.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -18,22 +17,24 @@ using namespace Kt;
namespace Net::Ethernet {
// Which device the frames actually go out of is the registry's decision;
// this layer only cares that something is there.
static const uint8_t* GetActiveNicMac() {
if (Drivers::Net::E1000::IsInitialized())
return Drivers::Net::E1000::GetMacAddress();
return Drivers::Net::E1000E::GetMacAddress();
return NetIf::ActiveMac();
}
static bool ActiveNicSend(const uint8_t* data, uint16_t length) {
if (Drivers::Net::E1000::IsInitialized())
return Drivers::Net::E1000::SendPacket(data, length);
return Drivers::Net::E1000E::SendPacket(data, length);
return NetIf::ActiveSend(data, length);
}
void Initialize() {
KernelLogStream(OK, "Net") << "Ethernet layer initialized";
}
const uint8_t* GetMacAddress() {
return NetIf::ActiveMac();
}
bool Send(const uint8_t* destMac, uint16_t etherType, const uint8_t* payload, uint16_t payloadLen) {
if (payload == nullptr || payloadLen == 0 || payloadLen > MAX_PAYLOAD_SIZE) {
return false;
+4 -1
View File
@@ -30,7 +30,10 @@ namespace Net::Ethernet {
// Send an Ethernet frame with the given EtherType and payload
bool Send(const uint8_t* destMac, uint16_t etherType, const uint8_t* payload, uint16_t payloadLen);
// Called by E1000 RX handler to dispatch received frames
// Called by a driver's RX handler to dispatch received frames
void OnFrameReceived(const uint8_t* data, uint16_t length);
// MAC address of the interface currently carrying traffic
const uint8_t* GetMacAddress();
}
+58 -13
View File
@@ -1,11 +1,12 @@
/*
* Net.cpp
* Network stack initialization
* Copyright (c) 2025 Daniel Hammer
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include "Net.hpp"
#include <Net/Ethernet.hpp>
#include <Net/NetIf.hpp>
#include <Net/Arp.hpp>
#include <Net/Ipv4.hpp>
#include <Net/Icmp.hpp>
@@ -15,6 +16,7 @@
#include <Net/NetConfig.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Drivers/Net/Wifi/Wifi.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -22,9 +24,57 @@ using namespace Kt;
namespace Net {
// A driver that never initialized has no link regardless of what its
// carrier register says.
static bool E1000LinkUp() {
return Drivers::Net::E1000::IsInitialized()
&& Drivers::Net::E1000::IsLinkUp();
}
static bool E1000ELinkUp() {
return Drivers::Net::E1000E::IsInitialized()
&& Drivers::Net::E1000E::IsLinkUp();
}
static void RegisterInterfaces() {
if (Drivers::Net::E1000::IsInitialized()) {
NetIf::Register({
"eth0", NetIf::Kind::Ethernet,
Drivers::Net::E1000::GetMacAddress,
Drivers::Net::E1000::SendPacket,
E1000LinkUp,
});
Drivers::Net::E1000::SetRxCallback(Ethernet::OnFrameReceived);
}
if (Drivers::Net::E1000E::IsInitialized()) {
NetIf::Register({
"eth1", NetIf::Kind::Ethernet,
Drivers::Net::E1000E::GetMacAddress,
Drivers::Net::E1000E::SendPacket,
E1000ELinkUp,
});
Drivers::Net::E1000E::SetRxCallback(Ethernet::OnFrameReceived);
}
// Wi-Fi registers whenever the adapter exists; its link only comes up
// once a network has been joined and keyed.
if (Drivers::Net::Wifi::IsPresent()) {
NetIf::Register({
"wlan0", NetIf::Kind::Wireless,
Drivers::Net::Wifi::GetMacAddress,
Drivers::Net::Wifi::SendPacket,
Drivers::Net::Wifi::IsLinkUp,
});
Drivers::Net::Wifi::SetRxCallback(Ethernet::OnFrameReceived);
}
}
void Initialize() {
if (!Drivers::Net::E1000::IsInitialized() && !Drivers::Net::E1000E::IsInitialized()) {
KernelLogStream(WARNING, "Net") << "No NIC initialized, skipping network stack";
RegisterInterfaces();
if (NetIf::Count() == 0) {
KernelLogStream(WARNING, "Net")
<< "No network interface found, skipping network stack";
return;
}
@@ -37,17 +87,12 @@ namespace Net {
Tcp::Initialize();
Socket::Initialize();
// Hook the active NIC's RX to our Ethernet dispatcher
if (Drivers::Net::E1000::IsInitialized()) {
Drivers::Net::E1000::SetRxCallback(Ethernet::OnFrameReceived);
} else {
Drivers::Net::E1000E::SetRxCallback(Ethernet::OnFrameReceived);
}
// Announce ourselves, but only if something is actually carrying
// traffic: a Wi-Fi-only machine has no link until it joins a network.
if (NetIf::AnyLinkUp()) Arp::SendRequest(GetIpAddress());
// Send a gratuitous ARP to announce ourselves on the network
Arp::SendRequest(GetIpAddress());
KernelLogStream(OK, "Net") << "Network stack initialized";
KernelLogStream(OK, "Net") << "Network stack initialized with "
<< (uint64_t)NetIf::Count() << " interface(s)";
}
}
+59
View File
@@ -0,0 +1,59 @@
/*
* NetIf.cpp
* Network interface registry.
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include "NetIf.hpp"
namespace Net::NetIf {
static Interface g_ifaces[MAX_INTERFACES];
static int g_count = 0;
static const uint8_t kZeroMac[6] = {};
bool Register(const Interface& iface) {
if (g_count >= MAX_INTERFACES) return false;
if (!iface.GetMac || !iface.Send || !iface.IsLinkUp) return false;
g_ifaces[g_count++] = iface;
return true;
}
int Count() { return g_count; }
const Interface* At(int index) {
if (index < 0 || index >= g_count) return nullptr;
return &g_ifaces[index];
}
const Interface* Active() {
if (g_count == 0) return nullptr;
for (int i = 0; i < g_count; i++)
if (g_ifaces[i].Type == Kind::Ethernet && g_ifaces[i].IsLinkUp())
return &g_ifaces[i];
for (int i = 0; i < g_count; i++)
if (g_ifaces[i].IsLinkUp())
return &g_ifaces[i];
return &g_ifaces[0];
}
const uint8_t* ActiveMac() {
const Interface* i = Active();
return i ? i->GetMac() : kZeroMac;
}
bool ActiveSend(const uint8_t* data, uint16_t length) {
const Interface* i = Active();
return i ? i->Send(data, length) : false;
}
bool AnyLinkUp() {
for (int i = 0; i < g_count; i++)
if (g_ifaces[i].IsLinkUp()) return true;
return false;
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
* NetIf.hpp
* Network interface registry.
*
* The Ethernet layer used to reach straight into the E1000/E1000E drivers.
* Wi-Fi is a third link-layer device that carries the same Ethernet frames
* once it is associated, so drivers now register a small vtable here and the
* stack talks to whichever interface currently has a link.
*
* Copyright (c) 2025-2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Net::NetIf {
enum class Kind : uint8_t {
Ethernet = 0,
Wireless = 1,
};
struct Interface {
const char* Name; // "eth0", "wlan0"
Kind Type;
const uint8_t* (*GetMac)();
bool (*Send)(const uint8_t* data, uint16_t length);
bool (*IsLinkUp)();
};
constexpr int MAX_INTERFACES = 4;
// Register a link-layer device. Order of registration decides ties.
bool Register(const Interface& iface);
int Count();
const Interface* At(int index);
// The interface traffic is currently going out of: the first registered
// interface reporting a link, with wired preferred over wireless so a
// plugged-in cable keeps winning. Falls back to the first registered
// interface when nothing reports a link, so ifconfig still has a MAC to
// show. Returns null when nothing is registered at all.
const Interface* Active();
// Convenience wrappers used by the Ethernet layer.
const uint8_t* ActiveMac();
bool ActiveSend(const uint8_t* data, uint16_t length);
bool AnyLinkUp();
}