Skip to content

nithikapidikiti-collab/tcp-ip-stack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

tcp-ip-stack

A userspace TCP/IP stack for macOS built on top of the kernel's utun virtual network interface. It implements Ethernet frame parsing, ARP, IPv4, ICMPv4 echo (ping), and a minimal TCP state machine — all without using BSD sockets or libc networking for the actual protocol logic.

License

MIT — Copyright © 2026 nithikapidikiti-collab

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.


Requirements

  • macOS (tested on macOS 13+)
  • Xcode Command Line Tools (xcode-select --install)
  • clang (comes with Xcode CLT)

Building

make

Running

The utun device requires root:

make run          # calls: sudo ./tcp-ip-stack

In a second terminal, configure the host side of the tunnel and test:

# Assign the host end of the tunnel (replace utun3 with whatever was printed)
sudo ifconfig utun3 10.0.0.1 10.0.0.4 up

# Ping our stack's IP
ping 10.0.0.4

# Connect TCP (stack responds to the three-way handshake)
nc 10.0.0.4 8080

Hit Ctrl-C to stop the stack.

Architecture

main.c
  └── utun_open()        open PF_SYSTEM / SYSPROTO_CONTROL socket
      select() loop
        └── utun_read()  strip 4-byte AF prefix → raw IP packet
            ip_dispatch()
              ├── icmp_handle()  echo reply
              └── tcp_handle()   state machine
        └── tcp_tick()   retransmission timer
            utun_write() prepend 4-byte AF_INET prefix → kernel

Layer breakdown

utun (utun.c)

Opens a PF_SYSTEM / SOCK_DGRAM / SYSPROTO_CONTROL socket, resolves the UTUN_CONTROL_NAME kernel control identifier with CTLIOCGINFO, and connect()s with sc_unit = 0 so the kernel picks the next available utunN interface.

Every packet read from the fd has a 4-byte address-family prefix (AF_INET in network byte order) prepended by the kernel; utun_read strips it. utun_write re-adds it before writing.

Ethernet (ethernet.c)

Parses the 14-byte Ethernet II header (destination MAC, source MAC, EtherType) and provides eth_build to construct reply frames. On utun there is no layer-2 framing, but this module is used by the ARP handler and is ready for a raw-socket or tap back-end.

ARP (arp.c)

Parses ARP packets, identifies requests for our IP (10.0.0.4), and builds an ARP reply. On utun no ARP frames arrive (the kernel handles neighbour resolution itself), so the module logs and skips the wire write; a tap or raw-socket back-end would call utun_write here.

IPv4 (ip.c)

Parses the 20-byte IP header, validates the Internet checksum, and dispatches to icmp_handle or tcp_handle based on the protocol field. Packets not destined for 10.0.0.4 are silently dropped.

ip_build / ip_checksum are used by ICMP and TCP to construct reply packets.

ICMPv4 (icmp.c)

Responds to echo-request (type 8) with an echo-reply (type 0). The original ICMP payload (identifier, sequence, data) is reflected verbatim; only the type and checksum fields change.

TCP (tcp.c)

Implements a minimal TCP state machine supporting up to 16 simultaneous connections:

State Description
LISTEN Implicit — any SYN on any port opens a slot.
SYN_RECEIVED SYN received; SYN-ACK sent.
ESTABLISHED Handshake complete; data flows.
CLOSE_WAIT FIN received; waiting to finish sending.
LAST_ACK Our FIN sent; waiting for peer's ACK.
CLOSED Connection gone; slot reused.

Handshake: on SYN, the stack allocates a connection, chooses ISN 0x12345678, and sends SYN-ACK. The peer's ACK advances the state to ESTABLISHED.

Data receive: in-order segments are ACK'd immediately. Out-of-order segments are dropped (no receive buffering).

Teardown: on FIN the stack sends ACK then immediately sends its own FIN (passive close), transitioning through CLOSE_WAIT → LAST_ACK → CLOSED.

Retransmission: every segment that carries data, SYN, or FIN is saved in a per-connection buffer. tcp_tick() is called every 100 ms from main; it fires a retransmit when the deadline passes and applies exponential back-off (initial 1 s, doubling, capped at 30 s, max 5 attempts).

Utilities (utils.c)

  • checksum() — one's-complement Internet checksum over an arbitrary buffer.
  • checksum_tcp() — same, but concatenates a pseudo-header with the TCP segment (used for TCP checksum computation).
  • hex_dump() — prints a labelled hex dump to stdout for debugging.

About

Userspace TCP/IP stack built from scratch in C — implements Ethernet/ARP, IPv4/ICMPv4, TCP handshake, data flow, and retransmission using a macOS utun virtual network interface. No libc networking — raw packet parsing all the way down.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors