This repository is designed as an educational deep-dive into low-level network architecture. Instead of relying on high-level libraries (like requests or Flask), this project builds a complete network ecosystem from the ground up using Python's bare-metal OS socket library.
By reading and running this code, students can understand exactly how computers discover each other, resolve names, frame data, and guarantee delivery over unreliable connections.
I separated the logic into small, independent files to simulate real, distinct network components:
| File | Role |
|---|---|
dhcp_server.py |
DHCP Server — assigns an IP address to the client via UDP. |
dns_server.py |
DNS Server — resolves my-app-server.local to an IP via UDP. |
app_server.py |
TCP App Server (HTTP Proxy) — receives a FETCH command and downloads the file over TCP. |
client.py |
TCP Client — runs the full sequence: DHCP → DNS → TCP fetch → save file. |
app_server_rudp.py |
RUDP App Server — same job as the TCP server but uses my custom reliable UDP protocol. |
client_rudp.py |
RUDP Client — same flow but uses my RUDP protocol for the transfer. |
- The client starts with no IP address. It sends a
DISCOVERUDP message to the DHCP Server at127.0.0.1:6767. The server replies with{"type": "OFFER", "assigned_ip": "127.0.0.2"}. - Now that it has an IP, the client sends a JSON query
{"domain": "my-app-server.local"}to the DNS Server at127.0.0.1:5353. The server replies with{"status": "SUCCESS", "ip": "127.0.0.3"}. - The client connects to the App Server at
127.0.0.3and sends aFETCH http://127.0.0.1:8080/test_file.txtcommand. - The App Server downloads the file from the local HTTP server (
python -m http.server 8080) and sends all the bytes back to the client. - The client saves the received file to disk —
downloaded_from_web.html(TCP) ordownloaded_rudp.html(RUDP).
Before a client can download a file, it needs an identity (IP) and a way to find the server (DNS).
- The Concept: When a device joins a network, it has a physical MAC address but no IP address. A DHCP server dynamically leases an IP to the device.
- The Implementation: The
dhcp_server.pylistens on UDP port6767forDISCOVERbroadcasts. It responds with a JSONOFFERcontaining an available IP (e.g.,127.0.0.2).
- The Concept: Humans remember names (
example.local), but networking hardware uses IP addresses (127.0.0.3). DNS translates names to IPs. - The Implementation: The
dns_server.pylistens on UDP port5353. It acts as a directory, checking a hardcoded dictionary for the requested hostname and returning the mapped IP address.
- The Concept: TCP is a continuous "stream" of bytes. If a server sends 1000 bytes, the client might receive it in chunks of 200, 500, and 300. How does the client know when the message is complete?
- The Solution (Length-Prefix Framing): In
app_server.py, before sending the actual data, the server prepends a 10-byte, zero-padded header representing the payload's exact size (e.g.,0000000065). - The client (
client.py) strictly reads these first 10 bytes, converts them to an integer, and keeps receiving data in a loop until exactly that many bytes are collected.
UDP is fast but unreliable—it drops packets, scrambles order, and provides no delivery confirmation. This project builds a custom transport protocol (RUDP) over UDP to ensure absolute reliability.
Every data packet requires metadata to track its state. The app_server_rudp.py packs an 11-byte binary header using struct.pack('!IIcH'):
- Sequence Number (4 bytes): Identifies the packet's exact order.
- Acknowledgment Number (4 bytes): Confirms which packets were received.
- Flags (1 byte): Single characters dictating the packet type (
Sfor SYN,Afor ACK,Dfor DATA,Ffor FIN). - Payload Length (2 bytes): The size of the attached data chunk.
If a server blasts data faster than the network can handle, packets will be dropped. RUDP controls this flow:
-
Go-Back-N (GBN): The server sends a "window" of packets without waiting for individual ACKs. If a timeout occurs (1 second) before receiving an ACK for the base packet, the server resets and retransmits the entire window from that base sequence.
-
AIMD (Additive Increase, Multiplicative Decrease): The server dynamically adjusts its speed.
- Additive Increase: For every successful ACK, the window size increases by 1 (capped at a maximum of 5).
- Multiplicative Decrease: If a packet is lost (triggering a timeout), the window size is immediately divided by 2 to relieve network congestion.
To prove the RUDP protocol actually works, the client must act maliciously.
In client_rudp.py, we inject deliberate faults into the network:
- Packet Loss (
SIMULATE_PACKET_LOSS = True): The client randomly ignores ~30% of incoming DATA packets, forcing the server to trigger its Go-Back-N timeout and retransmit. - Latency (
SIMULATE_LATENCY = True): Introduces random delays (0.1to0.4seconds) to test the server's timeout thresholds.
Run the files in separate terminal windows in the following exact order to simulate a full network boot-up:
- Start the local HTTP server (provides the payload to download):
python -m http.server 8080
2. **Start the Infrastructure:**
```bash
python dhcp_server.py
python dns_server.py
- Start the Reliable Server:
python app_server_rudp.py
- Run the Client:
python client_rudp.py
To strictly validate the custom protocol implementation, congestion control algorithms, and overall network behavior, all traffic was captured on the local Loopback adapter.
Below are the Wireshark screenshots and raw .pcapng capture files demonstrating the successful execution of all system phases, including connection establishment, packet loss recovery, and sliding window dynamics.
Wireshark filter used:
udp.port == 6767 or udp.port == 5353 or tcp.port == 2121 or udp.port == 2122
Shows the DHCP assignment, DNS resolution, and the full TCP proxy fetch with the framed response.
📥 Click here to download the raw Wireshark capture file (.pcapng)
Shows the custom SYN, SYN-ACK, and first DATA command exchange over raw UDP using the 11-byte binary header.
📥 Click here to download the raw Wireshark capture file (.pcapng)
Shows the full transfer with the custom 11-byte headers, a single data chunk, and the FIN packet, without any simulated packet loss.
📥 Click here to download the raw Wireshark capture file for the RUDP clean flow (.pcapng)
Shows the server timing out and retransmitting the same chunk multiple times because the client's SIMULATE_PACKET_LOSS flag dropped the incoming DATA packets. Proves the Go-Back-N retransmission loop works correctly.
📥 Click here to download the raw Wireshark capture file for the RUDP packet loss flow (.pcapng)
Shows the Go-Back-N sliding window session with delayed ACKs caused by the SIMULATE_LATENCY flag. The delay between each DATA chunk and its ACK is clearly visible in the packet timestamps.
📥 Click here to download the raw Wireshark capture file for the RUDP advanced flow (.pcapng)




