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.