Analysing a Network Protocol
gpredict is piece of software for tracking things in orbits, sometimes you
want to automatically point things at stuff in orbit. To get things pointed at
stuff in orbit we can use a rotator controller, gpredict
as a piece of radio
software has an antenna rotator controller built it. The gpredict
rotator
controller expects to speak to something over TCP.
I have not been able to find documentation for the protocol (I didn't look very
hard), I thought it would be fun to reverse engineer the protocol and write a
simple daemon. Earlier I took some first steps to see what gpredict
was
doing on the network.
If you want to play a long at home this is what I am going to do:
- set up a dummy daemon using netcat (nc -l localhost 4533)
- use tcpdump with -XX to watch all traffic (e.g. tcpdump -XX -ilo0 tcp and port 4533)
- send data from gpredict to the daemon (hit the 'engage' button on the antenna control screen)
- play with responses (type into the console running nc)
- look at the gpredict code starting here: https://github.com/csete/gpredict/blob/master/src/gtk-rot-ctrl.c
The Network traffic
$ nc -l 0.0.0.0 4533
p
P 180.00 45.00
When I press the 'engage' button, gpredict
sends a single lower case 'p', if
I press enter, sending a blank line, gredict
responds with a capital 'P' and
two numbers. To me these numbers look like an Az El pair, they correspond to
the values on the antenna control screen in gpredict
. No need for tcpdump
this time.
We have source avaialble
With only one half of the network protocol to look at, we can't get very far.
gpredict
is open source and there is a github mirror where we can browse
the source tree. The file names in the 'src' directory show some promising
results:
gtk-rot-ctrl.c
gtk-rot-ctrl.h
gtk-rot-knob.c
gtk-rot-knob.h
rotor-conf.c
rotor-conf.h
sat-pref-rot.c
sat-pref-rot.h
The pref and conf files, are probably configuration stuff, I have no idea what is in the knob file, but the gtk-rot-ctrl set of files is what we want. I confirmed this by picking a string in the UI of the relevant screen and grepping through the code for it. This can be troublesome if the software is heavily localised, but it this case I could track down the 'Engage' button to a comment in the code.
There are two functions used for network traffic, send
is used to send data
into a tcp connection, recv
is used to receive data from a TCP connection. If
we can find these in the code, we find where the software is generating network
traffic. Normally only a starting point, it is very common to wrap these two
functions into other convenience functions.
A grep through the code brings up a send
call in
send_rotctld_command. More grepping and we find that
send_rotctld_command
is called from two places, the get_pos function
(which I have to guess asks for the rotators positions) and the set_pos
function (which must try to set the rotators position).
The get_pos
function fills a format string with "p\x0a" and uses
send_rotcld_command
to send it. Looking up 0x0A in an ascii table shows it is
Line Feed(LF) also known as a newline on a unix system. It splits buffback
on
newlines using g_strsplit, looking to find two floating point numbers to
use as azimuth and elevation, one on each line.
get_pos
:
/* send command */
buff = g_strdup_printf("p\x0a");
retcode = send_rotctld_command(ctrl, buff, buffback, 128);
...
vbuff = g_strsplit(buffback, "\n", 3);
if ((vbuff[0] != NULL) && (vbuff[1] != NULL))
{
*az = g_strtod(vbuff[0], NULL);
*el = g_strtod(vbuff[1], NULL);
}
This piece of code shows up something really important, gpredict
is using a
single function to both send a command and gather the response from the remote
end. If we look at send_rotctld_command
the recv call is called right
after a send. Here we can see that gpredict
only does a single recv
to
gather responses, it is expecting a reply that fits into a single read. This is
a bug, but probably not one that really matters.
/* try to read answer */
size = recv(ctrl->sock, buffout, sizeout, 0);
The set_pos
function fills up a format string with a capital 'P', and two
floating point numbers. It doesn't do any parsing of the response, only looking
at the error code from the socket call.
set_pos
:
/* send command */
g_ascii_formatd(azstr, 8, "%7.2f", az);
g_ascii_formatd(elstr, 8, "%7.2f", el);
buff = g_strdup_printf("P %s %s\x0a", azstr, elstr);
retcode = send_rotctld_command(ctrl, buff, buffback, 128);
Write a Daemon
With this little bit of analysis we have enough to write an antenna control
daemon that gpredict
can speak to. The rotator control protocol has two
simple commands, a position query which expects the currect az/el across
separate lines and a position setter, which expects no response.
#!/usr/bin/env python
import socket
TCP_IP = '127.0.0.1'
TCP_PORT = 4533
BUFFER_SIZE = 100
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connection address:', addr
az = 0.0
el = 0.0
while 1:
data = conn.recv(BUFFER_SIZE)
if not data:
break
print("received data:", data)
if data == "p\n":
print("pos query at az:{} el: {}", az, el);
response = "{}\n{}\n".format(az, el)
print("responing with: \n {}".format(response))
conn.send(response)
elif data.startswith("P "):
values = data.split(" ")
print(values)
az = float(values[1])
el = float(values[2])
print("moving to az:{} el: {}".format( az, el));
conn.send(" ")
elif data == "q\n":
print("close command, shutting down")
conn.close()
exit()
else:
print("unknown command, closing socket")
conn.close()
exit()
Using the python TCP server example as a starting point it is easy to put
together a daemon that will listen to the rotator controller. The code should
be pretty straight forward to read, we process the commands documented earlier.
There is one addition that I didn't see in the code at first. There is a quit
command that does not use the normal wrapper and instead uses send
directly.
This command was easy to handle.
This is how I approach network problems, whether in code I have written or code that is completely new to me. Hopefully if you have been following along at home the example above is straightforward to read.