After finishing my command line speed tester written in Rust, I didn’t have a proper blog to document this process. A few days ago I wrapped up a simple blogging script in Python so hopefully it works good enough to explain how everything works.
By now I have already figured out the whole protocol for performing a speed test but I will write all the steps that I took so you can learn how to reverse engineer a simple protocol.
The code that I wrote can be found at https://github.com/gkbrk/speedtest-rust.
Finding the TCP stream in Wireshark
First of all lets open Wireshark and start sniffing. This allows us to find and view any connection made from our machine. (Wireshark Screenshot)
After getting Wireshark ready I will go to speedtest.net and start a speed test. Then I will start checking Wireshark for possible connections.
By trial and error, I found the connection in Wireshark. It was connecting to speedtest.turk.net (a Turkish ISP, which makes sense since I’m in Turkey). (DNS response and TCP stream screenshot)
After finding the connection, we can see that it uses port 8080, which is called http-alt in Wireshark.
If we right click the connection and do “Follow TCP Stream”. If we found the right stream, we should see the data and we can start decoding the protocol. (Follow TCP Stream dialog screenshot)
Understanding the protocol
By just looking at the data, we can tell that it’s a plaintext protocol. That means instead of binary data, it uses text. This makes things a lot easier for us.
The HI Command
Here’s the first part of the data. C is client and S is the server.
<C> HI <S> HELLO 2.1 2013-08-14.01
When the client sends the HI command, the server responds with its version.
The best part of plaintext protocols is that you can test them with a tool like netcat, without writing a single line of code. To test the HI command, let’s open a terminal and run nc speedtest.turk.net 8080.
This creates a TCP stream that we can use to test the HI command. If we write HI and press enter; the server sends us its version, thinking that we are the speedtest.net app.
$ nc speedtest.turk.net 8080 HI HELLO 2.1 2013-08-14.01
The PING Command
Looking at the rest of the data, we see that the client sends a ping command followed by a timestamp. In response, the server sends “PONG timestamp”. It goes like this.
<C> PING 1418661866099 <S> PONG 1418661866349
This happens 20 times. The original implementation takes the highest ping and displays it, mine gets the average and displays it.
This can be tested with netcat just like the HI command.
The DOWNLOAD Command
After doing the handshake (the HI command) and the ping test, it is time to actually take a look at the download function.
It works like this; the client sends the download command followed by the number of bytes it wants to download, and the server responds with that many bytes, including the newline at the end of the response.
NOTE: The original implementation always responds with the first 8 bytes “DOWNLOAD”, a space, and repeating “JABCDEFGHI”.
It goes like this:
C: DOWNLOAD 14 S: DOWNLOAD JABC C: DOWNLOAD 5 S: DOWN C: DOWNLOAD 50 S: DOWNLOAD JABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHI
Normally you ask for much more data to do a reliable speed test. You can test the DOWNLOAD function in netcat with small numbers like the ones above.
The UPLOAD Command
The upload test is similar to the download test. To upload data to the server, you first send the UPLOAD command, followed by the number of bytes to upload and a zero. Then you upload the bytes. The server responds with “OK BYTES 0”.
NOTE: There is a small detail that I missed. I actually fixed the bug in my code while writing this. When you send the data, instead of sending the number of bytes you said in the command, you need to send the number of bytes minus the number of bytes in the command itself.
With the note in mind, it goes like this:
C: UPLOAD 20 0 C: RANDOM1 S: OK 20 0
That is 7 bytes. That is because the length of “UPLOAD 20 0” is 11. If we add the newline after the upload command and the newline after the bytes to it, that makes 13. And 20-13 is 7.
The QUIT Command
There isn’t much to explain about the QUIT command. You send it, the server closes the connection without a response. You don’t need to send it, but to comply with the original implementation, I do.
After sending this command, the server terminates the connection and you can’t read or write to it anymore.
Implementing the Actual Speed Testing
Now that you know the whole network protocol of the speed testing servers, you can implement this protocol in your programs.
The servers send timestamps with the responses to PING and UPLOAD commands. I don’t know if the original client uses those values for calculating the speed but I just use time::precise_time_ns() from the Rust time library before and after downloading/uploading the data and subtract those to get the time it took to download/upload it.
Using this method I got accurate results that matched with those on speedtest.net, so this method works.
For gathering the actual results, my implementation does:
- 20 pings and gets the average
- Downloads 1 MB 4 times and gets the average
- Uploads 1 MB 2 times and gets the average
Getting the Serverlist
To get the coordinates(lat, lon) of the user, check out http://www.speedtest.net/speedtest-config.php.
To get a list of all the servers and their coordinates, check out http://www.speedtest.net/speedtest-servers-static.php.
You just need to parse these pages with an XML parser or use some regex trickery to get the server list data.