Files
nt-lite/code/kernel/ethernet.c
T

621 lines
20 KiB
C

// ethernet.c - tiny pre-lwIP Ethernet/ARP/IPv4/ICMP shim.
//
// Goals:
// * prove NIC RX/TX works before lwIP is integrated;
// * reply to ARP for a configured static IPv4 address;
// * reply to ICMP echo requests for that address;
// * provide a tiny NetPingIpv4() smoke-test path;
// * auto-configure QEMU's default user-mode network only when the MAC looks
// like the QEMU OUI (52:54:00), so real hardware is not assigned a bogus IP.
//
// This is not intended to grow into a full stack. When lwIP arrives, keep the
// NET_DEVICE send/receive boundary and replace this protocol code with lwIP's
// netif input path.
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <ke_debug.h>
#include <ke_spinlock.h>
#include <ethernet.h>
#define NET_MAX_ETH_STATES 8
#define NET_ARP_CACHE_SIZE 8
#define ETH_ADDR_LEN NET_ETHERNET_ADDRESS_LENGTH
#define ETH_HDR_LEN NET_ETHERNET_HEADER_LENGTH
#define ETH_TYPE_IPV4 0x0800u
#define ETH_TYPE_ARP 0x0806u
#define ARP_HTYPE_ETHERNET 0x0001u
#define ARP_PTYPE_IPV4 0x0800u
#define ARP_OPER_REQUEST 0x0001u
#define ARP_OPER_REPLY 0x0002u
#define ARP_PACKET_LEN 28u
#define IPV4_PROTO_ICMP 1u
#define ICMP_ECHO_REPLY 0u
#define ICMP_ECHO_REQUEST 8u
#define NET_ETH_MAX_FRAME NET_ETHERNET_MAX_FRAME
#define NET_LOG_LIMIT 16u
static const uint8_t gBroadcastMac[ETH_ADDR_LEN] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
typedef struct NET_ARP_ENTRY {
bool Valid;
uint32_t Ip;
uint8_t HardwareAddress[ETH_ADDR_LEN];
} NET_ARP_ENTRY;
typedef struct NET_ETH_STATE {
bool Present;
bool Ipv4Configured;
bool GatewayResolved;
uint32_t Address;
uint32_t Netmask;
uint32_t Gateway;
uint8_t GatewayHardwareAddress[ETH_ADDR_LEN];
NET_ARP_ENTRY ArpCache[NET_ARP_CACHE_SIZE];
uint32_t ArpNextReplace;
bool PendingPing;
uint32_t PendingPingTarget;
uint32_t PendingPingNextHop;
uint32_t RxFrames;
uint32_t TxFrames;
uint16_t IcmpEchoSequence;
uint32_t ArpFrames;
uint32_t Ipv4Frames;
uint32_t IcmpFrames;
} NET_ETH_STATE;
static NET_ETH_STATE gEthStates[NET_MAX_ETH_STATES];
static SPINLOCK gEthLock = SPINLOCK_INIT;
static uint16_t NetLoadBe16(const uint8_t* p) {
return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
}
static uint32_t NetLoadBe32(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 void NetStoreBe16(uint8_t* p, uint16_t v) {
p[0] = (uint8_t)(v >> 8);
p[1] = (uint8_t)v;
}
static void NetStoreBe32(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 void NetPrintIpv4(uint32_t ip) {
DbgPrint("%u.%u.%u.%u",
(unsigned)((ip >> 24) & 0xFFu),
(unsigned)((ip >> 16) & 0xFFu),
(unsigned)((ip >> 8) & 0xFFu),
(unsigned)(ip & 0xFFu));
}
static void NetPrintMac(const uint8_t* mac) {
DbgPrint("%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
static bool NetHasEthernetAddress(const NET_DEVICE* device) {
return device && device->HardwareAddressLength == ETH_ADDR_LEN;
}
static bool NetIsQemuMac(const NET_DEVICE* device) {
return NetHasEthernetAddress(device) &&
device->HardwareAddress[0] == 0x52 &&
device->HardwareAddress[1] == 0x54 &&
device->HardwareAddress[2] == 0x00;
}
static bool NetIsBroadcastMac(const uint8_t* mac) {
return mac && memcmp(mac, gBroadcastMac, ETH_ADDR_LEN) == 0;
}
static bool NetIsMulticastMac(const uint8_t* mac) {
return mac && (mac[0] & 0x01u) != 0;
}
static bool NetEthernetFrameIsForDevice(const NET_DEVICE* device, const uint8_t* frame) {
if (!NetHasEthernetAddress(device) || !frame)
return false;
return memcmp(frame, device->HardwareAddress, ETH_ADDR_LEN) == 0 ||
NetIsBroadcastMac(frame) ||
NetIsMulticastMac(frame);
}
static bool NetIpv4SameSubnet(uint32_t a, uint32_t b, uint32_t mask) {
return ((a ^ b) & mask) == 0;
}
static NET_ETH_STATE* NetGetEthState(NET_DEVICE* device) {
if (!device || device->Index >= NET_MAX_ETH_STATES)
return (NET_ETH_STATE*)0;
return &gEthStates[device->Index];
}
static uint16_t NetChecksum(const uint8_t* data, size_t length) {
uint32_t sum = 0;
while (length >= 2) {
sum += NetLoadBe16(data);
data += 2;
length -= 2;
}
if (length)
sum += (uint16_t)((uint16_t)data[0] << 8);
while (sum >> 16)
sum = (sum & 0xFFFFu) + (sum >> 16);
return (uint16_t)~sum;
}
static void NetBuildEthernetHeader(uint8_t* frame,
const uint8_t* dst,
const uint8_t* src,
uint16_t etherType) {
memcpy(frame, dst, ETH_ADDR_LEN);
memcpy(frame + ETH_ADDR_LEN, src, ETH_ADDR_LEN);
NetStoreBe16(frame + 12, etherType);
}
static bool NetArpLookup(NET_ETH_STATE* state, uint32_t ip, uint8_t* macOut) {
if (!state)
return false;
for (unsigned i = 0; i < NET_ARP_CACHE_SIZE; ++i) {
if (state->ArpCache[i].Valid && state->ArpCache[i].Ip == ip) {
if (macOut)
memcpy(macOut, state->ArpCache[i].HardwareAddress, ETH_ADDR_LEN);
return true;
}
}
return false;
}
static void NetArpRemember(NET_ETH_STATE* state, uint32_t ip, const uint8_t* mac) {
if (!state || !mac || ip == 0)
return;
for (unsigned i = 0; i < NET_ARP_CACHE_SIZE; ++i) {
if (state->ArpCache[i].Valid && state->ArpCache[i].Ip == ip) {
memcpy(state->ArpCache[i].HardwareAddress, mac, ETH_ADDR_LEN);
return;
}
}
unsigned slot = state->ArpNextReplace % NET_ARP_CACHE_SIZE;
state->ArpNextReplace = (state->ArpNextReplace + 1) % NET_ARP_CACHE_SIZE;
state->ArpCache[slot].Valid = true;
state->ArpCache[slot].Ip = ip;
memcpy(state->ArpCache[slot].HardwareAddress, mac, ETH_ADDR_LEN);
}
static KSTATUS NetSendArp(NET_DEVICE* device,
uint16_t operation,
const uint8_t* targetMac,
uint32_t targetIp) {
NET_ETH_STATE* state = NetGetEthState(device);
if (!state || !state->Ipv4Configured)
return STATUS_NOT_FOUND;
if (!NetHasEthernetAddress(device))
return STATUS_INVALID_PARAMETER;
if (operation == ARP_OPER_REPLY && !targetMac)
return STATUS_INVALID_PARAMETER;
uint8_t frame[ETH_HDR_LEN + ARP_PACKET_LEN];
const uint8_t* ethDst = (operation == ARP_OPER_REQUEST) ? gBroadcastMac : targetMac;
NetBuildEthernetHeader(frame, ethDst, device->HardwareAddress, ETH_TYPE_ARP);
uint8_t* arp = frame + ETH_HDR_LEN;
NetStoreBe16(arp + 0, ARP_HTYPE_ETHERNET);
NetStoreBe16(arp + 2, ARP_PTYPE_IPV4);
arp[4] = ETH_ADDR_LEN;
arp[5] = 4;
NetStoreBe16(arp + 6, operation);
memcpy(arp + 8, device->HardwareAddress, ETH_ADDR_LEN);
NetStoreBe32(arp + 14, state->Address);
if (operation == ARP_OPER_REQUEST)
memset(arp + 18, 0, ETH_ADDR_LEN);
else
memcpy(arp + 18, targetMac, ETH_ADDR_LEN);
NetStoreBe32(arp + 24, targetIp);
KSTATUS st = NetSendFrame(device->Index, frame, sizeof(frame));
if (KSUCCESS(st))
state->TxFrames++;
return st;
}
static void NetSendArpRequest(NET_DEVICE* device, uint32_t targetIp) {
DbgPrint("NET: %s tx ARP who-has ", device->Name);
NetPrintIpv4(targetIp);
DbgPrint("\n");
KSTATUS st = NetSendArp(device, ARP_OPER_REQUEST, gBroadcastMac, targetIp);
if (!KSUCCESS(st))
DbgPrint("NET: %s ARP probe failed: 0x%x\n", device->Name, st);
}
static KSTATUS NetSendIcmpEchoRequestResolved(NET_DEVICE* device,
NET_ETH_STATE* state,
uint32_t targetIp,
const uint8_t* nextHopMac) {
if (!device || !state || !state->Ipv4Configured || !nextHopMac)
return STATUS_INVALID_PARAMETER;
enum {
IPV4_HEADER_LEN = 20,
ICMP_HEADER_LEN = 8,
ICMP_PAYLOAD_LEN = 32,
ICMP_TOTAL_LEN = ICMP_HEADER_LEN + ICMP_PAYLOAD_LEN,
IPV4_TOTAL_LEN = IPV4_HEADER_LEN + ICMP_TOTAL_LEN,
FRAME_LEN = ETH_HDR_LEN + IPV4_TOTAL_LEN
};
uint8_t frame[FRAME_LEN];
NetBuildEthernetHeader(frame,
nextHopMac,
device->HardwareAddress,
ETH_TYPE_IPV4);
uint8_t* ip = frame + ETH_HDR_LEN;
memset(ip, 0, IPV4_TOTAL_LEN);
ip[0] = 0x45; // IPv4, 20-byte header.
ip[1] = 0; // DSCP/ECN.
NetStoreBe16(ip + 2, IPV4_TOTAL_LEN);
NetStoreBe16(ip + 4, state->IcmpEchoSequence);
NetStoreBe16(ip + 6, 0x4000); // Don't fragment.
ip[8] = 64; // TTL.
ip[9] = IPV4_PROTO_ICMP;
NetStoreBe32(ip + 12, state->Address);
NetStoreBe32(ip + 16, targetIp);
NetStoreBe16(ip + 10, NetChecksum(ip, IPV4_HEADER_LEN));
uint8_t* icmp = ip + IPV4_HEADER_LEN;
icmp[0] = ICMP_ECHO_REQUEST;
icmp[1] = 0;
NetStoreBe16(icmp + 4, 0x4b4e); // temporary pre-lwIP ping ID: 'KN'.
NetStoreBe16(icmp + 6, state->IcmpEchoSequence);
for (unsigned i = 0; i < ICMP_PAYLOAD_LEN; ++i)
icmp[ICMP_HEADER_LEN + i] = (uint8_t)('A' + (i % 26));
NetStoreBe16(icmp + 2, NetChecksum(icmp, ICMP_TOTAL_LEN));
DbgPrint("NET: %s tx ICMP echo request to ", device->Name);
NetPrintIpv4(targetIp);
DbgPrint(" seq=%u\n", (unsigned)state->IcmpEchoSequence);
KSTATUS st = NetSendFrame(device->Index, frame, FRAME_LEN);
if (KSUCCESS(st)) {
state->TxFrames++;
state->IcmpEchoSequence++;
} else {
DbgPrint("NET: %s ICMP echo request failed: 0x%x\n", device->Name, st);
}
return st;
}
static KSTATUS NetPingIpv4OnDevice(NET_DEVICE* device,
NET_ETH_STATE* state,
uint32_t targetIp) {
if (!device || !state || !state->Ipv4Configured || targetIp == 0)
return STATUS_INVALID_PARAMETER;
uint32_t nextHop = targetIp;
if (!NetIpv4SameSubnet(state->Address, targetIp, state->Netmask)) {
if (state->Gateway == 0)
return STATUS_NOT_FOUND;
nextHop = state->Gateway;
}
uint8_t nextHopMac[ETH_ADDR_LEN];
if (NetArpLookup(state, nextHop, nextHopMac))
return NetSendIcmpEchoRequestResolved(device, state, targetIp, nextHopMac);
state->PendingPing = true;
state->PendingPingTarget = targetIp;
state->PendingPingNextHop = nextHop;
DbgPrint("NET: %s ping ", device->Name);
NetPrintIpv4(targetIp);
DbgPrint(" waiting for ARP ");
NetPrintIpv4(nextHop);
DbgPrint("\n");
NetSendArpRequest(device, nextHop);
return STATUS_PENDING;
}
static void NetHandleArp(NET_DEVICE* device, NET_ETH_STATE* state,
const uint8_t* frame, size_t length) {
if (length < ETH_HDR_LEN + ARP_PACKET_LEN)
return;
const uint8_t* arp = frame + ETH_HDR_LEN;
if (NetLoadBe16(arp + 0) != ARP_HTYPE_ETHERNET ||
NetLoadBe16(arp + 2) != ARP_PTYPE_IPV4 ||
arp[4] != ETH_ADDR_LEN || arp[5] != 4)
return;
uint16_t op = NetLoadBe16(arp + 6);
const uint8_t* senderMac = arp + 8;
uint32_t senderIp = NetLoadBe32(arp + 14);
uint32_t targetIp = NetLoadBe32(arp + 24);
state->ArpFrames++;
if (state->ArpFrames <= NET_LOG_LIMIT) {
DbgPrint("NET: %s rx ARP %s ",
device->Name,
op == ARP_OPER_REQUEST ? "request" :
op == ARP_OPER_REPLY ? "reply" : "op");
NetPrintIpv4(senderIp);
DbgPrint(" ");
NetPrintMac(senderMac);
DbgPrint(" -> ");
NetPrintIpv4(targetIp);
DbgPrint("\n");
}
if ((op == ARP_OPER_REQUEST || op == ARP_OPER_REPLY) && senderIp != 0)
NetArpRemember(state, senderIp, senderMac);
if (op == ARP_OPER_REPLY && state->Ipv4Configured && senderIp == state->Gateway) {
memcpy(state->GatewayHardwareAddress, senderMac, ETH_ADDR_LEN);
state->GatewayResolved = true;
DbgPrint("NET: %s gateway resolved ", device->Name);
NetPrintIpv4(senderIp);
DbgPrint(" = ");
NetPrintMac(senderMac);
DbgPrint("\n");
}
if (op == ARP_OPER_REPLY && state->PendingPing && senderIp == state->PendingPingNextHop) {
uint32_t pingTarget = state->PendingPingTarget;
state->PendingPing = false;
(void)NetSendIcmpEchoRequestResolved(device, state, pingTarget, senderMac);
}
if (op == ARP_OPER_REQUEST && state->Ipv4Configured && targetIp == state->Address) {
KSTATUS st = NetSendArp(device, ARP_OPER_REPLY, senderMac, senderIp);
if (KSUCCESS(st)) {
DbgPrint("NET: %s tx ARP reply for ", device->Name);
NetPrintIpv4(state->Address);
DbgPrint("\n");
} else {
DbgPrint("NET: %s ARP reply failed: 0x%x\n", device->Name, st);
}
}
}
static void NetHandleIcmpEcho(NET_DEVICE* device, NET_ETH_STATE* state,
const uint8_t* frame, size_t length,
const uint8_t* ip, size_t ipHeaderLength,
size_t totalLength) {
(void)length;
if (totalLength < ipHeaderLength + 8)
return;
const uint8_t* icmp = ip + ipHeaderLength;
size_t icmpLength = totalLength - ipHeaderLength;
if (NetChecksum(icmp, icmpLength) != 0)
return;
if (icmp[0] != ICMP_ECHO_REQUEST || icmp[1] != 0)
return;
if (ETH_HDR_LEN + totalLength > NET_ETH_MAX_FRAME)
return;
uint8_t out[NET_ETH_MAX_FRAME];
NetBuildEthernetHeader(out, frame + 6, device->HardwareAddress, ETH_TYPE_IPV4);
memcpy(out + ETH_HDR_LEN, ip, totalLength);
uint8_t* outIp = out + ETH_HDR_LEN;
uint8_t* outIcmp = outIp + ipHeaderLength;
// Swap IPv4 endpoints and rebuild the IPv4 header checksum.
memcpy(outIp + 12, ip + 16, 4);
memcpy(outIp + 16, ip + 12, 4);
outIp[8] = 64;
outIp[10] = 0;
outIp[11] = 0;
NetStoreBe16(outIp + 10, NetChecksum(outIp, ipHeaderLength));
// Convert echo request to echo reply and rebuild the ICMP checksum.
outIcmp[0] = ICMP_ECHO_REPLY;
outIcmp[2] = 0;
outIcmp[3] = 0;
NetStoreBe16(outIcmp + 2, NetChecksum(outIcmp, icmpLength));
KSTATUS st = NetSendFrame(device->Index, out, ETH_HDR_LEN + totalLength);
if (KSUCCESS(st)) {
state->TxFrames++;
if (state->IcmpFrames <= NET_LOG_LIMIT) {
DbgPrint("NET: %s tx ICMP echo reply to ", device->Name);
NetPrintIpv4(NetLoadBe32(ip + 12));
DbgPrint("\n");
}
} else {
DbgPrint("NET: %s ICMP reply failed: 0x%x\n", device->Name, st);
}
}
static void NetHandleIpv4(NET_DEVICE* device, NET_ETH_STATE* state,
const uint8_t* frame, size_t length) {
if (!state->Ipv4Configured || length < ETH_HDR_LEN + 20)
return;
const uint8_t* ip = frame + ETH_HDR_LEN;
uint8_t version = (uint8_t)(ip[0] >> 4);
size_t ipHeaderLength = (size_t)(ip[0] & 0x0Fu) * 4u;
if (version != 4 || ipHeaderLength < 20 || length < ETH_HDR_LEN + ipHeaderLength)
return;
size_t totalLength = NetLoadBe16(ip + 2);
if (totalLength < ipHeaderLength || length < ETH_HDR_LEN + totalLength)
return;
if (NetChecksum(ip, ipHeaderLength) != 0)
return;
uint16_t fragment = NetLoadBe16(ip + 6);
if (fragment & 0x3FFFu)
return;
uint32_t dstIp = NetLoadBe32(ip + 16);
if (dstIp != state->Address)
return;
state->Ipv4Frames++;
if (ip[9] == IPV4_PROTO_ICMP) {
if (totalLength < ipHeaderLength + 8)
return;
const uint8_t* icmp = ip + ipHeaderLength;
size_t icmpLength = totalLength - ipHeaderLength;
if (NetChecksum(icmp, icmpLength) != 0)
return;
state->IcmpFrames++;
if (icmp[0] == ICMP_ECHO_REPLY && icmp[1] == 0) {
DbgPrint("NET: %s rx ICMP echo reply from ", device->Name);
NetPrintIpv4(NetLoadBe32(ip + 12));
DbgPrint(" seq=%u\n", (unsigned)NetLoadBe16(icmp + 6));
return;
}
if (state->IcmpFrames <= NET_LOG_LIMIT) {
DbgPrint("NET: %s rx ICMP type=%u code=%u from ",
device->Name,
(unsigned)icmp[0],
(unsigned)icmp[1]);
NetPrintIpv4(NetLoadBe32(ip + 12));
DbgPrint("\n");
}
NetHandleIcmpEcho(device, state, frame, length, ip, ipHeaderLength, totalLength);
}
}
__declspec(dllexport) KSTATUS NetConfigureIpv4Static(uint32_t deviceIndex,
uint32_t address,
uint32_t netmask,
uint32_t gateway) {
if (deviceIndex >= NET_MAX_ETH_STATES || address == 0)
return STATUS_INVALID_PARAMETER;
NET_DEVICE* device = NetGetDevice(deviceIndex);
if (!device)
return STATUS_NOT_FOUND;
if (!NetHasEthernetAddress(device))
return STATUS_NOT_SUPPORTED;
uintptr_t flags = KeAcquireSpinlock(&gEthLock);
NET_ETH_STATE* state = &gEthStates[deviceIndex];
state->Ipv4Configured = true;
state->GatewayResolved = false;
state->PendingPing = false;
state->IcmpEchoSequence = 1;
state->Address = address;
state->Netmask = netmask;
state->Gateway = gateway;
state->ArpNextReplace = 0;
memset(state->GatewayHardwareAddress, 0, sizeof(state->GatewayHardwareAddress));
memset(state->ArpCache, 0, sizeof(state->ArpCache));
KeReleaseSpinlock(&gEthLock, flags);
DbgPrint("NET: eth%u IPv4 ", deviceIndex);
NetPrintIpv4(address);
DbgPrint(" mask ");
NetPrintIpv4(netmask);
DbgPrint(" gw ");
NetPrintIpv4(gateway);
DbgPrint("\n");
return STATUS_SUCCESS;
}
__declspec(dllexport) KSTATUS NetPingIpv4(uint32_t deviceIndex, uint32_t targetIp) {
NET_DEVICE* device = NetGetDevice(deviceIndex);
if (!device || deviceIndex >= NET_MAX_ETH_STATES)
return STATUS_NOT_FOUND;
NET_ETH_STATE* state = &gEthStates[deviceIndex];
if (!state->Present || !state->Ipv4Configured)
return STATUS_NOT_FOUND;
return NetPingIpv4OnDevice(device, state, targetIp);
}
void NetEthernetAttachDevice(NET_DEVICE* device) {
if (!device || device->Index >= NET_MAX_ETH_STATES)
return;
if (!NetHasEthernetAddress(device)) {
DbgPrint("NET: %s has unsupported hardware address length %u; Ethernet shim disabled\n",
device->Name, (unsigned)device->HardwareAddressLength);
return;
}
uintptr_t flags = KeAcquireSpinlock(&gEthLock);
NET_ETH_STATE* state = &gEthStates[device->Index];
memset(state, 0, sizeof(*state));
state->Present = true;
KeReleaseSpinlock(&gEthLock, flags);
if (NetIsQemuMac(device)) {
NetConfigureIpv4Static(device->Index,
NET_IPV4_ADDR(10, 0, 2, 15),
NET_IPV4_ADDR(255, 255, 255, 0),
NET_IPV4_ADDR(10, 0, 2, 2));
state = &gEthStates[device->Index];
(void)NetPingIpv4OnDevice(device, state, state->Gateway);
} else {
DbgPrint("NET: %s has no IPv4 address yet; ARP/ICMP replies disabled\n",
device->Name);
}
}
void NetEthernetReceive(NET_DEVICE* device, const void* frame, size_t length) {
NET_ETH_STATE* state = NetGetEthState(device);
if (!state || !state->Present || !frame || length < ETH_HDR_LEN)
return;
const uint8_t* b = (const uint8_t*)frame;
if (!NetEthernetFrameIsForDevice(device, b))
return;
uint16_t etherType = NetLoadBe16(b + 12);
state->RxFrames++;
switch (etherType) {
case ETH_TYPE_ARP:
NetHandleArp(device, state, b, length);
break;
case ETH_TYPE_IPV4:
NetHandleIpv4(device, state, b, length);
break;
default:
break;
}
}