?

Log in

 
 
22 August 2008 @ 12:04 am
tcpdump-remote-server & tcpdump-local-client: Capture packets remotely and view them locally  
I have several of those Linksys WRT54Gs that I've hacked and put Linux on (http://www.openwrt.org/ ).  They are combination WiFi access point , ethernet switch, and broadband routers.  One of the reasons I hack them is so I can do cool stuff like the following.  This pair of scripts lets you sniff packets on one machine, but watch the sniff in near realtime on another.  I use them to watch all the packet traffic on my home network.

You could just SSH into the remote machine and fire up wireshark (formerly known as ethereal), but in my case I didn't have enough disk space to install wireshark and all of its dependencies on it.  It has tcpdump on it, but I still want the nice wireshark GUI.  I could capture to a file with tcpdump,  transfer the file to my workstation, then open it up with wireshark.  But I want it to be real-time, and I'd like to have the process partially or wholly automated.

So I wrote these two scripts.  The first, tcpdump-remote-server, runs on the remote system that you want to perform a sniff on.  You give it two arguments, both are optional, (but whose default values may only be useful to me).  The first is the filter rule (in libpcap aka tcpdump format) and the second is the network interface you want to sniff on.  You need permission to open the network interface, which usually requires root access.  You use it like this:

BEGIN  EXAMPLE
root@bort:~# ./tcpdump-remote-server '! tcp port 11111' br0
listening on port 11111

END EXAMPLE

Now it's listening on port 11111.  Anyone who connects to it will get a mouthful of pcap file.  Here's the code:

BEGIN tcpdump-remote-server
#!/bin/sh

RULE="$1";
IF=$2;
PORT=11111
LEN=1500

if [ -z "$RULE" ]; then
        #RULE="! ip net 10.0.0.0/27";
        RULE="! tcp port $PORT";
fi

if [ -z "$IF" ]; then
        IF=br0;
        #IF=vlan1;
        #IF=prism0;
fi

CHILD_FILE=`mktemp /tmp/tcpdump-remote-server-child-XXXXXX`;
trap "rm $CHILD_FILE" 1 2 15 EXIT;
cat > $CHILD_FILE <<HERE
#!/bin/sh
tcpdump -U $CNT -s $LEN -i $IF -w - "$RULE" 2> /dev/null
HERE

chmod a+x $CHILD_FILE

while true; do
        echo listening on port $PORT
        nc -l -p $PORT -e $CHILD_FILE;
        echo done
        sleep 1;
done

END tcpdump-remote-server

This listens on port 1111, waits for a connection to the port, then sends the binary output of tcpdump to whoever connected to the port.  It only handles one connection at a time, but after disconnection it will accept a new connection.

It has a default filter command which filters out its own traffic. If you override it on the command line,it would be a good idea to add the default rule back in conjunction with whatever your custom filter is.  It also defaults to the br0 network interface, which happens to be very convenient for me.  Control-C it several times in succession to stop the server, or use the kill or killall commands from another window.

BTW, this requires netcat (nc), specifically  a version of netcat compiled to have the -e flag.  (I've noticed there are lot of versions of netcat around, some even with different incompatible argument syntax.)  The -e flag causes netcat to execute a command after it initiates or receives a network connection (depending on whether it's being used as a client or server--server, in our case, hence the -l and -p flags). The -e flag is sometimes considered a security risk, but if someone logged on to your system knows what netcat is, knows why the -e command is a security risk (aka fun to play with), then nothing will stop that person from installing their own version of netcat (or anything else).

The code generates a temporary shell script in /tmp (ugly, I know).  This is necessary because the -e flag in netcat is implemented as the execve() rather than system() system call.  That means it doesn't do any command line parsing.  I try to avoid making shell scripts that call other shell scripts, but there's no way toaround it here, so at least I have it dynamically create and delete the secondary script.  It does so in as safe a way as the mktemp command is able, which I suspect isn't all that secure.  No guarantees.  I run this on what is essentially an appliance, with no local users around to exploit file name race conditions.  Plus, I'm in denial.

The second script, tcpdump-local-client, will connect to the server and run wireshark, connecting the output of the server to the input of wireshark, which will render the packet capture in all of its GUI wunderfulness.  You run it, and it pops up wireshark.  You don't even need to be root on the local machine!  The first and only argument is the name of the machine running the server.  Here's the code:

BEGIN tcpdump-local-client
#!/bin/sh

BORT="$1"
if [ -z "$BORT" ]; then
        BORT=bort;
fi
PORT=11111;

nc -v $BORT $PORT | wireshark -k -i -

END tcpdump-local-client

It connects to the the server and pipes it into wireshark, which immediately starts to "capture" packets from the server.  The only gotcha is that if you stop the capture in wireshark, you can't restart it.  You have to exit wireshark and start the client script again.

Using these on my Linksys WRT54g, I can see the traffic going to and from every system on my network, on any system on my network.  It's worth noting that this is incredibly insecure.  Anyone can see everything on your network.  Better would be to use stunnel or socat to do an authenticated and encrypted SSL connection between client and server rather than unauthenticated & unencrypted netcat, but that requires setting up a public/private keys and such.  At the very least, set up some firewall rules from prevent people outside your network from connecting to the server.  Or do what I do and only run it when you need it, and live in denial about the risk!