// 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 #include #include #include #include #include #include #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; } }