What happens if you write a TCP stack in Python?

During Hacker School, I wanted to understand networking better, and I
decided to write a miniature TCP stack as part of that. I was much
more comfortable with Python than C and I’d recently discovered the
scapy networking library
which made sending packets
really easy.

I didn’t care much about proper error handling or anything; I just
wanted to get one webpage and declare victory :)

Step 1: the TCP handshake

I started out by doing a TCP handshake with Google! (this won’t
necessarily run correctly, but illustrates the principles). I’ve
commented each line.

The way a TCP handshake works is:

me: SYN

google: SYNACK!

me: ACK!!!

Pretty simple, right? Let’s put it in code.

12345678910111213141516171819

# My local network IPsrc_ip="192.168.0.11"# Google's IPdest_ip="96.127.250.29"# IP header: this is coming from me, and going to Googleip_header=IP(dst=dest_ip,src=src_ip)# Specify a large random port number for myself (59333),# and port 80 for Google The "S" flag means this is# a SYN packetsyn=TCP(dport=80,sport=59333,ack=0,flags="S")# Send the SYN packet to Google# scapy uses '/' to combine packets with headersresponse=srp(ip_header/syn)# Add the sequence number ack=TCP(dport=80,sport=self.src_port,ack=response.seq,flags="A")# Reply with the ACKsrp(ip_header/ack)

Wait, sequence numbers?

What’s all this about sequence numbers? The whole point of TCP is to
make sure you can resend packets if some of them go missing. Sequence
numbers are a way to check if you’ve missed packets. So let’s say that
Google sends me 4 packets, size 110, 120, 200, and 500 bytes. Let’s
pretend the initial sequence number is 0. Then those packets will have
sequence numbers 0, 110, 230, and 430.

So if I suddenly got a 100-byte packet with a sequence number of 2000,
that would mean I missed a packet! The next sequence number should be
930!

How can Google know that I missed the packet? Every time I receive a
packet from Google, I need to send an ACK (“I got the packet with
sequence number 230, thanks!”). If the Google server notices I haven’t
ACKed a packet, then it can resend it!

The TCP protocol is extremely complicated and has all kinds of rate
limiting logic in it, but we’re not going to talk about any of that.
This is all you’ll need to know about TCP for this post!

The sequences of packets from Google and ACKs from me looked something
like: P P P A P P P P P A P P A P P P P A. Google was sending me
packets way faster than my program could keep up and send ACKs.
Then, hilariously, Google’s server would assume that there were
network problems causing me to not ACK its packets.

And it would eventually reset the connection because it would decide
there were connection problems.

But the connection was fine! My program was totally responding! It was
just that my Python program was way too slow to respond to packets in
the millisecond times it expected.

(edit: this diagnosis seems to be incorrect :) you can
read some discussion
about what may be actually going on here)

life lessons

If you’re actually writing a production TCP stack, don’t use Python.
(surprise!) Also, the TCP spec is really complicated, but you can get
servers to reply to you even if your implementation is extremely sketchy.

I was really happy that it actually worked, though! The ARP spoofing
was extremely finicky, but I wrote a version of curl using it which
worked about 25% of the time. You can see all the absurd code at
https://github.com/jvns/teeceepee/.

I think this was actually way more fun and instructive than trying to
write a TCP stack in an appropriate language like C :)