DEV Community

Cover image for What happens when you send an Ethernet frame? I wrote 289 lessons to find out.
Tanay Kedia
Tanay Kedia

Posted on

What happens when you send an Ethernet frame? I wrote 289 lessons to find out.

Every networking course I found had the same problem.

Either it's pure theory — OSI model diagrams, "TCP does a three-way handshake," done. Or it skips straight to socket() and connect() and treats everything underneath as a black box.

Nothing showed me what actually happens between the bits on a wire and curl https://example.com returning a response.

So I wrote 289 lessons that build the entire network stack from scratch in C.

What "from scratch" means

You don't call libraries. You construct the bytes yourself.

Here's how you build an Ethernet frame in the course:

int nfs_frame_build(const uint8_t *dst, const uint8_t *src,
                    uint16_t ethertype, const uint8_t *payload,
                    size_t payload_len, uint8_t *out, size_t out_sz) {
    size_t eff_payload = payload_len;
    if (eff_payload < NFS_ETH_MIN_DATA)
        eff_payload = NFS_ETH_MIN_DATA;

    size_t total = NFS_ETH_HLEN + eff_payload;
    memset(out, 0, total);

    /* Destination MAC */
    memcpy(out, dst, NFS_ETH_ALEN);
    /* Source MAC */
    memcpy(out + NFS_ETH_ALEN, src, NFS_ETH_ALEN);
    /* EtherType in network byte order */
    uint16_t et_net = htons(ethertype);
    memcpy(out + 12, &et_net, 2);

    if (payload && payload_len > 0)
        memcpy(out + NFS_ETH_HLEN, payload, payload_len);

    return (int)total;
}
Enter fullscreen mode Exit fullscreen mode

6 bytes destination MAC. 6 bytes source MAC. 2 bytes EtherType. Payload. That's an Ethernet frame. You built it. You understand it.

Same approach for everything else — ARP, IP, ICMP, TCP, DNS, TLS.

The 15 phases

The course is organized bottom-up:

Phases 1-3: Bits → Frames → Packets
You start with encoding schemes and bit manipulation, build Ethernet frames with raw sockets, then implement IP headers with proper checksums, write an ICMP ping, and build a router.

Phase 4: TCP (40 lessons)
This is the big one. You implement the full TCP state machine — three-way handshake, sliding window, retransmission, SACK, congestion control (Reno, CUBIC, BBR), TIME-WAIT. 40 lessons because TCP is that deep.

Phases 5-7: Sockets, Protocols, TLS
You build an HTTP parser, a DNS resolver, implement the TLS 1.3 handshake including Diffie-Hellman key exchange, AES-GCM encryption, and X.509 certificate parsing.

Phase 8: The capstone
Everything comes together. You build a complete userspace TCP/IP stack in C that can make HTTPS requests. Your code, your stack, real traffic.

Phases 9-14: Go deeper
Linux kernel networking internals (sk_buff, NAPI, netfilter), eBPF/XDP programs, container networking, building a CNI plugin, service mesh. The stuff you debug at 3am in production but never learned properly.

Phase 15: DDS & Robotics
Bonus phase covering DDS middleware used in ROS 2 and real-time systems.

What a lesson looks like

Each lesson has:

  • A README explaining the concept
  • C (or Python) source code
  • A Makefile
  • Tests
  • Exercises

You clone the repo, cd into a lesson, run make, and start building. No IDE required, no special tools. Just gcc and a Linux machine.

Here's DNS name encoding from the DNS lesson — turning example.com into the wire format that RFC 1035 specifies:

int nfs_dns_name_encode(const char *name, uint8_t *out, size_t out_sz) {
    size_t pos = 0;
    size_t i = 0;
    size_t name_len = strlen(name);

    while (i < name_len) {
        const char *dot = strchr(name + i, '.');
        size_t label_len = dot ? (size_t)(dot - (name + i))
                               : name_len - i;

        if (label_len == 0) { i++; continue; }
        if (label_len > 63) return -1;

        out[pos++] = (uint8_t)label_len;
        memcpy(out + pos, name + i, label_len);
        pos += label_len;
        i += label_len + (dot ? 1 : 0);
    }
    out[pos++] = 0; /* root terminator */
    return (int)pos;
}
Enter fullscreen mode Exit fullscreen mode

example.com becomes \x07example\x03com\x00. Length-prefixed labels, null-terminated. You read the RFC, you write the code, you understand the format.

Why build instead of read

I used to think I understood TCP. I'd read the textbook diagrams, I could explain the three-way handshake on a whiteboard.

Then I tried to implement a sliding window with SACK support and realized I understood almost nothing. What happens when an ACK arrives for a segment you already retransmitted? How do you handle window scaling across 32-bit sequence space? What does CUBIC actually do differently from Reno?

Building forces you to answer questions that reading lets you skip.

The repo

289 lessons. 15 phases. MIT licensed. No signup, no paywall.

GitHub: github.com/TanayK07/networking-from-scratch

Site: networkingfromscratch.vercel.app

If you've ever wanted to understand what actually happens between your application and the wire, this is the course I wish existed when I started.

Contributions welcome — especially reviews of the kernel internals and eBPF phases. Open an issue or PR on GitHub.

Top comments (0)