A Network Stack
Talk to the outside world using nothing you didn't write. Drive the Ethernet NIC at the register level, then hand-build ARP, IP, ICMP, and TCP — and watch your OS answer a ping and open a real socket.
Networking has a reputation for being impossibly complex. It isn't — it's a stack of simple layers, each wrapping the one below. You'll build it bottom-up: get raw frames on and off the wire, then Ethernet, then ARP, IP, ICMP, and finally TCP. Each layer is understandable on its own.
6.1 The NIC and DMA rings
An Ethernet controller (the course uses the well-documented Intel e1000) moves packets via DMA ring buffers: a circular array of descriptors, each pointing at a buffer. You set up the rings, and the NIC writes received packets into them and reads packets to send from them. You talk to the NIC through memory-mapped registers.
struct rx_desc {
UINT64 addr; // physical buffer address
UINT16 length;
UINT16 checksum;
UINT8 status; // bit 0 = Descriptor Done
UINT8 errors;
UINT16 special;
} __attribute__((packed));
// poll for received packets
void e1000_poll_rx(void) {
while (rx_ring[rx_tail].status & 1) { // DD bit set = packet ready
handle_frame(rx_buffers[rx_tail], rx_ring[rx_tail].length);
rx_ring[rx_tail].status = 0; // hand it back to the NIC
rx_tail = (rx_tail + 1) % RX_COUNT;
}
}6.2 Ethernet and ARP
Every frame starts with an Ethernet header: destination MAC, source MAC, and a type field. Before you can send to an IP, you need its MAC — that's ARP. You broadcast "who has 192.168.1.1?" and the owner replies with its MAC, which you cache. ARP is the smallest complete request/response protocol, which makes it the perfect first one to build.
void arp_request(UINT8 *target_ip) {
struct arp_packet a = {0};
a.htype = htons(1); // Ethernet
a.ptype = htons(0x0800); // IPv4
a.hlen = 6; a.plen = 4;
a.oper = htons(1); // request
memcpy(a.sha, my_mac, 6);
memcpy(a.spa, my_ip, 4);
memcpy(a.tpa, target_ip, 4);
eth_send_broadcast(ETH_ARP, &a, sizeof a);
}6.3 IP and ICMP — answer a ping
IP adds addressing and a checksum on top of Ethernet. ICMP rides on IP, and the simplest ICMP exchange is echo — ping. When a ping request arrives, you swap source and destination, change the type from request to reply, fix the checksum, and send it back. The moment another machine's ping gets a reply from your OS is genuinely thrilling.
void icmp_handle(struct ip_hdr *ip, struct icmp_hdr *icmp) {
if (icmp->type != 8) return; // only echo requests
icmp->type = 0; // 0 = echo reply
icmp->checksum = 0;
icmp->checksum = checksum16(icmp, icmp_len);
ip_send(ip->src, IP_ICMP, icmp, icmp_len); // straight back
}Once ICMP and TCP worked, ForgeOS ran a port scan against a real home router and got correct results — the proof that the hand-written stack actually spoke to real network hardware, not just a loopback. When yours answers a ping from another machine on your LAN, you've hit the same milestone.
6.4 TCP — the hard one
TCP is the deep end: a state machine (SYN, SYN-ACK, ACK to connect; FIN to close), sequence numbers, acknowledgements, and retransmission. Start with just enough to open a connection and exchange data — the three-way handshake and basic send/receive. Don't build congestion control yet; get a socket open first.
void tcp_handle(struct tcp_hdr *t) {
if (t->flags & SYN) {
conn->their_seq = ntohl(t->seq) + 1;
conn->my_seq = random_isn();
tcp_send(conn, SYN | ACK, 0, 0); // SYN-ACK
conn->state = SYN_RECEIVED;
} else if ((t->flags & ACK) && conn->state == SYN_RECEIVED) {
conn->state = ESTABLISHED; // connection open!
}
}6.5 Settings, Wi-Fi status, and NetMon
With a working stack, expose it. A Settings page shows the interface, IP, and gateway; a NetMon app shows live traffic and connection state. This is where Module 04's UI toolkit and Module 06's stack meet — the network becomes something the user can see, not just packets on a wire.
- A NIC driver with working DMA receive/transmit rings
- Ethernet framing and ARP address resolution
- IPv4 with correct checksums, and ICMP echo — your OS answers ping
- A TCP handshake that opens a real connection
- Network state surfaced in Settings and a NetMon app
Module 07 — Run DOOM
Port doomgeneric onto your OS: WAD loading off your filesystem, your framebuffer as its display, your keyboard as its input. The universal proof that you built something real.
Get lifetime access — $149 →