15 KiB
TCP vs. UDP: The Two Fundamental Methods of Sending Data Across the Internet
These are the two transport-layer protocols that sit directly above IP (Internet Protocol) in the network stack. They are the substrate — the physical/OS-level guarantees (or lack thereof) that every application protocol must build on.
1. TCP — Transmission Control Protocol
What it is: A connection-oriented, reliable, ordered byte stream between two computers.
How It Works
-
Three-way handshake — Before any data flows, the sender and receiver exchange three packets:
SYN→ (sender says "I want to talk")SYN-ACK← (receiver says "OK, I'm ready")ACK→ (sender says "Great, let's go")
This creates state on both sides: sequence numbers, window sizes, buffers.
-
Data transfer — Bytes are numbered with sequence numbers. The receiver sends
ACKpackets confirming receipt. If the sender doesn't get an ACK within a timeout, it retransmits. -
Flow control — The receiver tells the sender how much buffer space it has left. The sender slows down if the receiver is overwhelmed.
-
Congestion control — TCP dynamically adjusts its sending rate based on inferred network congestion (packet loss = slow down, success = speed up).
-
Connection teardown — A four-packet exchange (
FIN,FIN-ACK,ACK) to close gracefully.
Key Properties
| Property | Description |
|---|---|
| Reliability | Every byte arrives, in order, or the connection fails |
| Ordering | Bytes arrive in the exact order sent |
| Connection-oriented | Both sides maintain state |
| Stream-oriented | No message boundaries — just a continuous stream of bytes |
| Detectable | The SYN/SYN-ACK/ACK pattern is unmistakable to deep packet inspection |
The Cost
- Latency: One round trip before any data can be sent
- State overhead: Both ends must maintain connection state
- Head-of-line blocking: A lost packet stalls all subsequent data
- Visible handshake: The SYN/SYN-ACK/ACK pattern can be fingerprinted and blocked
2. UDP — User Datagram Protocol
What it is: A connectionless, unreliable, message-oriented datagram service.
How It Works
-
No handshake — The sender just puts bytes on the wire. No prior communication with the receiver. No state created on either side.
-
Fire and forget — The sender calls
sendto()and the OS fires the datagram out the network interface. There is no acknowledgment, no retransmission, no guarantee of delivery. -
Message boundaries preserved — Each
sendto()call produces exactly one datagram. The receiver'srecvfrom()returns exactly that datagram (or nothing). Messages are not split or merged. -
No flow control, no congestion control — The application is responsible for managing its own sending rate.
Key Properties
| Property | Description |
|---|---|
| Unreliability | Packets may be lost, duplicated, or arrive out of order |
| No connection | Zero state on either side |
| Message-oriented | Datagram boundaries are preserved |
| Low latency | Data can be sent immediately — zero round trips before the first byte |
| Hard to detect | A single UDP datagram is just a blob of bytes — no handshake pattern |
The Cost
No delivery guarantees. The application must handle loss, reordering, and duplication itself (if it cares).
The Critical Difference
| Property | TCP | UDP |
|---|---|---|
| Handshake | 3-way (SYN, SYN-ACK, ACK) | None |
| State | Connection state on both sides | Stateless |
| Reliability | Guaranteed delivery, in order | Best effort, no guarantees |
| Message boundaries | Stream (no boundaries) | Datagram (boundaries preserved) |
| Latency before first byte | 1 RTT minimum | Zero |
| Detectable pattern | Yes — unmistakable handshake | No — indistinguishable from noise |
| Censorship resistance | Low — handshake can be blocked | High — no pattern to block |
| MTU awareness | Fragments transparently | Application must manage packet size |
The MTU Boundary (Why 1472 Bytes Matters)
The Maximum Transmission Unit (MTU) for Ethernet is 1500 bytes. This is a physical/hardware constraint:
Ethernet frame: 1500 bytes (hardware limit)
- IP header: 20 bytes (OS adds this)
- UDP header: 8 bytes (OS adds this)
= UDP payload: 1472 bytes (what your application can send)
If you send more than 1472 bytes over UDP, the IP layer fragments the datagram into multiple packets. The receiver's OS reassembles them before delivering to the application. But fragmentation:
- Creates multiple packets that can be blocked individually
- Makes the communication visible (fragmented packets have identifiable headers)
- Fails if any fragment is lost (the entire datagram is lost)
If you keep your message under 1472 bytes, it fits in exactly one Ethernet frame. This is the single-packet guarantee — the message is atomic at the hardware level.
The No-Handshake Property (Why It Matters for Censorship)
| Protocol | Handshake | Round trips before data | Detectable pattern |
|---|---|---|---|
| TCP | SYN, SYN-ACK, ACK | 1 | Yes — unmistakable |
| TLS | ClientHello, ServerHello+Cert, Finished | 2+ | Yes — certificate, cipher suites |
| HTTP/3 (QUIC) | QUIC Initial + Handshake | 1 | Partially — Initial packet has structure |
| WireGuard | Handshake initiation + response | 1 | Minimal but recognizable |
| Nostr over UDP | None | 0 — fire and forget | No pattern at all |
A Nostr event is self-validating — it carries its own signature. The signature replaces the handshake. The receiver validates the event using only the pubkey in the event. No prior relationship, no shared secrets, no session setup.
This means:
- No handshake pattern to fingerprint — a single UDP datagram is just bytes on the wire
- No active probing vulnerability — the adversary cannot distinguish a Nostr relay from any other UDP service by sending a probe, because there's no handshake to respond to
- No amplification attack vector — the response (if any) is smaller than the request
- Stateless relays — no connection state to exhaust with SYN floods
The Great Dichotomy Applied
From TheGreatDichomety.md: things that happen inside computers vs. outside computers.
-
TCP is a complex inside-computer construct. It creates an abstraction (a reliable byte stream) that doesn't exist in the physical network. The OS kernel maintains connection state, sequence numbers, timers, retransmission buffers — all patterns of bits inside the computer. The handshake is a negotiation between two computers' internal state machines.
-
UDP is closer to the outside-computer reality. It maps almost directly to what the network hardware does: fire packets at the wire and hope they arrive. No state, no negotiation, no abstraction. Just bytes.
The Nostr-over-UDP approach exploits this: by eliminating the handshake (the inside-computer negotiation), the communication becomes indistinguishable from the raw physical substrate. The adversary cannot distinguish your message from any other random noise on the wire, because at the physical level, that's exactly what it is.
Summary Diagram
flowchart LR
subgraph Application
A[Nostr Event<br/>self-validating]
end
subgraph Transport
B[UDP<br/>no handshake<br/>no state]
C[TCP<br/>3-way handshake<br/>connection state]
end
subgraph Network
D[IP<br/>packets]
end
subgraph Physical
E[Ethernet frames<br/>MTU: 1500 bytes]
end
A --> B
A --> C
B --> D
C --> D
D --> E
style B fill:#4a4,color:#fff
style C fill:#a44,color:#fff
The green path (UDP) is the one explored in this project — it leaves no trace, requires no negotiation, and exploits the physical properties of the substrate (MTU boundaries, no handshake pattern) to resist censorship.
References
2Approaches.md— The cypherpunk problem and approaches to solving itudp_nostr_demo/README.md— UDP Nostr demo documentationudp_nostr_demo/no_handshake.md— The no-handshake property in depthudp_nostr_demo/mtu_exploration.md— MTU as a cypherpunk toolrethinking_nostr/max_single_packet_event.md— Binary event format byte layoutTheGreatDichomety.md— Inside computers vs. outside computers
3. Raw IP — Can You Skip TCP and UDP Entirely?
Short answer: Yes, you can send data directly over IP using raw sockets (SOCK_RAW). But there are serious practical restrictions that make it nearly useless on the open internet.
How It Works
The IP header has an 8-bit protocol field that identifies what transport protocol is carrying the payload:
| Protocol Number | Name | Common Use |
|---|---|---|
| 1 | ICMP | ping, traceroute, error reporting |
| 2 | IGMP | Multicast group management |
| 6 | TCP | Web, email, file transfer |
| 17 | UDP | DNS, video, gaming, Nostr |
| 132 | SCTP | Telephony, signaling |
| 33 | DCCP | Streaming, congestion-controlled UDP |
You can put any value in this field. If you write a raw socket program that sets protocol field to, say, 249, your packets will be sent out onto the wire with that protocol number. A receiver with a matching raw socket can read them.
Raw Socket Code (Linux)
import socket
# SOCK_RAW lets you craft your own IP packets
# IPPROTO_RAW (255) means you supply the entire IP header yourself
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
# Or use a custom protocol number (e.g., 249)
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, 249)
The Three Levels of Socket
Application
|
|--- SOCK_STREAM (TCP) -- You give bytes, OS handles everything
|--- SOCK_DGRAM (UDP) -- You give datagrams, OS adds UDP + IP headers
|--- SOCK_RAW (IP) -- You supply IP header (or let OS add it partially)
|
Network interface (Ethernet)
The Problems with Raw IP
1. Root privileges required
On Linux, creating a raw socket requires CAP_NET_RAW capability or root. Normal user programs cannot use SOCK_RAW. This alone makes it impractical for most applications.
2. Firewalls block non-standard protocols by default
Almost every firewall, NAT gateway, and router on the internet is configured to pass only TCP (protocol 6) and UDP (protocol 17). Everything else is dropped. Some also pass ICMP (protocol 1) for diagnostics.
If you send packets with protocol 249, they will be dropped by the first router that doesn't recognize the protocol. Your packets never reach the destination.
3. NAT requires TCP or UDP
Network Address Translation (NAT) — used by virtually every home router and corporate firewall — works by tracking ports. TCP and UDP have 16-bit port numbers (0-65535) that NAT uses to map internal addresses to external ones.
Raw IP has no port concept. There is no way for a NAT to track which internal computer a raw IP packet belongs to. The NAT will either drop the packet or misroute it.
This is the killer problem. Even if you could get raw IP packets through firewalls, they cannot traverse NAT.
4. OS network stacks are not designed for it
- No demultiplexing: The OS uses the protocol field to decide which socket gets the packet. If no socket is listening for protocol 249, the packet is dropped silently.
- No buffering: Raw sockets don't get the same buffer management as TCP/UDP.
- No standard API: Raw socket behavior varies between Linux, BSD, and Windows.
5. Middleboxes interfere
Deep packet inspection (DPI) devices, intrusion detection systems, and enterprise firewalls are trained on TCP and UDP. They may drop or flag packets with unusual protocol numbers as anomalous or malicious.
The Practical Reality
| Method | Traverses NAT? | Traverses Firewalls? | Works on open internet? |
|---|---|---|---|
| TCP | Yes | Yes (common ports) | Yes |
| UDP | Yes | Yes (common ports) | Yes |
| ICMP (ping) | Sometimes | Sometimes (often rate-limited) | Limited |
| Raw IP (custom protocol) | No | No | No |
What About Protocols Like QUIC?
QUIC (HTTP/3) runs over UDP, not raw IP. It uses UDP port 443. This is why it works — it inherits all the NAT and firewall traversal properties of UDP. The "QUIC runs over UDP" design was a deliberate choice to ensure it would work on the existing internet.
The Great Dichotomy Applied
-
TCP and UDP are inside-computer abstractions that the OS provides to applications. They are implemented in the kernel. But they are also the only protocols that the outside-computer network infrastructure (routers, NATs, firewalls) has been built to handle.
-
Raw IP is closer to the wire — you're speaking the language of routers directly. But the network infrastructure has evolved to expect TCP or UDP on top. The outside-computer world has standardized on these two protocols.
The asymmetry: you can send raw IP packets from your computer, but the network between you and the destination will almost certainly drop them. The internet is not a neutral packet-delivery service — it is a TCP/UDP delivery service with everything else as second-class citizens.
Summary: The Full Protocol Stack
flowchart TD
subgraph "Application Layer"
HTTP[DNS / HTTP / Nostr / etc.]
end
subgraph "Transport Layer"
TCP[TCP<br/>protocol 6<br/>reliable, ordered, connection]
UDP[UDP<br/>protocol 17<br/>unreliable, stateless, datagram]
RAW[Raw IP<br/>protocol any<br/>you craft everything]
end
subgraph "Network Layer"
IP[IP<br/>packets with protocol field]
end
subgraph "Link Layer"
ETH[Ethernet / WiFi<br/>frames with MTU 1500]
end
HTTP --> TCP
HTTP --> UDP
HTTP -.-> RAW
TCP --> IP
UDP --> IP
RAW --> IP
IP --> ETH
style RAW fill:#a4a,color:#fff
style TCP fill:#a44,color:#fff
style UDP fill:#4a4,color:#fff
- Green path (UDP): Works on the open internet. Used by Nostr-over-UDP, DNS, QUIC, video streaming.
- Red path (TCP): Works on the open internet. Used by HTTP, email, SSH. Has detectable handshake.
- Purple path (Raw IP): Works only on controlled networks (LAN, VPN, data center). Cannot traverse NAT. Blocked by firewalls. Requires root.
For censorship-resistant communication, UDP is the sweet spot: it works on the real internet, has no handshake pattern, and can be made to look like random noise.
References
2Approaches.md— The cypherpunk problem and approaches to solving itudp_nostr_demo/README.md— UDP Nostr demo documentationudp_nostr_demo/no_handshake.md— The no-handshake property in depthudp_nostr_demo/mtu_exploration.md— MTU as a cypherpunk toolrethinking_nostr/max_single_packet_event.md— Binary event format byte layoutTheGreatDichomety.md— Inside computers vs. outside computers