feat: wi-fi - join WPA2/WPA3-PSK networks and carry traffic like ethernet
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 53
|
||||
#define MONTAUK_BUILD_NUMBER 69
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
// =============================================================================
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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, >k, >kLen, &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, >k, >kLen, &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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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)";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user