Last year since reading Julia’s article What happens if you write a TCP stack in Python?, and I have been planning to implement a TCP stack in Ruby language. But my procrastination has been winning since then.
This week, I decided to give it a try and it turns out to be really fun. In this post, I’m going to follow Julia’s steps and write down some implementation details.
What we would like to do here is, and I quote from Julia’s blog:
- open a raw network socket that lets me send TCP packets
- send a HTTP request to
- get and parse a response
In the implementation, a gem called PacketFu was used to read and write packets. I don’t think I could write the stack in such a short time without it, it’s really awesome.
Step 1: the TCP handshake
The TCP three-way handshake is:
- me: SYN
- google: SYNACK
- me: ACK
Pseduo code could be something like below:
send_syn_packet() read_response() send_ack_packet()
PacketFu, sending a packet is pretty simple:
For ack packet, we could just set
pkt.tcp_flags.ack to be
As to read response, we need to filter packets from the network interface in a different process/thread.
filter parameter for
PacketFu::Capture is pretty interesting. It’s a
bpf filter and you could learn more about the syntax in the documentation here.
tcp and src 220.127.116.11 means that we’d like to filter tcp packets from ip
18.104.22.168, which is the ip of google.com.
tcpdump also uses
bpf filter to filter packets. Let’s say we only care about the
SYNACK packets. With tcpdump, we could do:
$ sudo tcpdump -i eth0 'tcp=18'
What does the
tcp=18 argument mean? Here is the TCP Header Format:
We could see that the last six bits of fourteenth byte stand for tcp flags, and the previous two bits are both 0. So for
SYNACK packets, tcp flags would be:
010010, the value would be 16 + 2 = 18. So
tcp=18 means only dumping
Bpf is super powerful, and if you would like to know more examples, read
In this step, I’m gonna ignore the part of how SEQ and ACK number work, you could read this article to learn more.
Step 2: Kernel sends a RST after receiving the SYNACK packet
Julia described this in her article, instead of what we expect how it would work, it didn’t.
As the picture shown, after receiving
SYNACK packet from google.com, a
RST packet was sent (obviously not by us).
I will just quote Julia’s explaining here:
my Python/Ruby program: SYN google: SYNACK my kernel: lol wtf I never asked for this! RST! my Python/Ruby program: ... :(
ARP spoofing to pretend a different IP address, and someone commented about using tap/tun interfaces instead. I tried both, but none of them worked for me (maybe sth is wrong with my implementations). I failed to find a way to let the kernel ignore the packet. And after digging for a while, if I used the ip of my nameserver as
src_ip in the packet, it worked! I’m not exactly sure how this works, but it fixed my problem. I will leave this as a question and ask some network folks later.
Now, the three-way handshake works!
Step 3: get a web page!
This is pretty easy, what we should do is:
- send a packet containing a HTTP GET request
- listen for packets in response
- parse the packet
- decide what to do based on tcp flag
Implementing the last one will cost you some time probably, since it comes down to some parts of TCP Finite State Machine.
Constructing a HTTP
Get request is quite easy, just include
GET some_path HTTP/1.0\r\nHost: hostname\r\n\r\n in your
I used a seperate thread to listen for packets from the destination IP, and after parsing the packet, it will send the result back to main thread and let it respond based on its state and packet tcp flags.
I’m really glad that I finally give this a try and got it working. Thanks to Julia for her article and code, I really learned a lot from them.
The last time I wrote packet related codes was like 5 or 6 years ago during collegue’s networking class. Mostly with C language back then. Thanks to the
PacketFu gem, I could avoid most of the dirty work.
Anyway, this is much more fun than I expected. Try it with your preferred language, and have some fun too!