The other day, I was playing around with a statsd server. I wanted to hit it with some UDP packets using the CLI.
Following the minimum-working-example reasoning process (my favorite!), I soon found myself using netcat to setup up a simple UDP "server" and "client".
The network tool netcat is also called the "TCP/IP swiss army knife".
What I didn't know is that there are many netcat flavors like netcat-traditional, netcat-openbsd, GNU netcat, as well as other netcat-like tools like ncat, socat.
I am using Debian, so I searched and found the 3 following packages:
# apt-cache search netcat --names-only netcat-openbsd - TCP/IP swiss army knife netcat - TCP/IP swiss army knife -- transitional package netcat-traditional - TCP/IP swiss army knife
Let's examine each.
The netcat package is a dummy/transitional package:
# apt-cache show netcat | grep Description-en -A 2 Description-en: TCP/IP swiss army knife -- transitional package This is a "dummy" package that depends on lenny's default version of netcat, to ease upgrades. It may be safely removed.
The netcat-traditional is very old and it is also named netcat 1.10:
Netcat was created in the year 1995 and was developed until first months of the 1996, when it reached version 1.10, released by Avian Research (avian.org). This is the traditional netcat, Copyright: © 1995-1996, Avian Research
The GNU netcat is quite old too:
Current Version: GNU Netcat 0.7.1 Released on: 11 Jan 2004
The netcat-openbsd looks promising. One interesting point is that it is not in fact the OpenBSD code. Rather it is the OpenBSD code with Debian patches/changes:
# apt-cache policy netcat-openbsd netcat-openbsd: Installed: 1.130-3 Candidate: 1.130-3 Version table: *** 1.130-3 500 500 http://ftp.gr.debian.org/debian stretch/main amd64 Packages 500 http://httpredir.debian.org/debian stretch/main amd64 Packages 500 http://http.debian.net/debian stretch/main amd64 Packages 100 /var/lib/dpkg/status
So there we have it, we want to use netcat-openbsd.
But which version do we already use?
To find out which version of netcat you are currently using, you can follow some symlinks:
$ which nc /bin/nc $ ls -al /bin/nc lrwxrwxrwx 1 root root 20 Nov 29 18:17 /bin/nc -> /etc/alternatives/nc $ ls -al /etc/alternatives/nc lrwxrwxrwx 1 root root 15 Dec 27 18:18 /etc/alternatives/nc -> /bin/nc.openbsd $ ls -al /bin/nc.openbsd -rwxr-xr-x 1 root root 31208 Mar 3 2017 /bin/nc.openbsd
BTW, you can use the freaking cool namei command for the same job:
$ namei `which nc` f: /bin/nc d / d bin l nc -> /etc/alternatives/nc d / d etc d alternatives l nc -> /bin/nc.openbsd d / d bin - nc.openbsd
Finally, make sure you read the correct man page, depending on the version of netcat you are using:
$ man nc $ man nc.openbsd $ man nc.traditional
Another interesting thing here is that although the man nc.openbsd page states:
-l Used to specify that nc should listen for an incoming connection rather than initiate a connection to a remote host. It is an error to use this option in conjunction with the -p, -s, or -z options. Additionally, any timeouts specified with the -w option are ignored.
It is quite OK to use -l and -p options together:
# /bin/nc.openbsd -l -p 9876
This appears to be a fix from the Debian team, without the relevant documentation (man page) update...
Fun with netcat and UDP
First, set up a netcat server, listening on port 9876, waiting for UDP packets:
$ /bin/nc.openbsd -v -4 -l -p 9876 -u -k -w 0
The server is now waiting.
Use another shell to fire up a netcat client and send a UDP packet to the server:
$ echo -e lala | /bin/nc.openbsd -v -4 -w 0 -u localhost 9876
Indeed, the server receives the message:
$ /bin/nc.openbsd -v -4 -l -p 9876 -u -k -w 0 XXlala
Seems nice and easy right? Actually, a lot of things went awry until I got it to work.
There seems to be a very strange behavior of netcat:
When nc is listening to a UDP socket, it "locks on" to the source port and source IP of the first packet it receives.
The solution for this is to use the -k option on the server side:
-k Forces nc to stay listening for another connection after its current connection is completed. It is an error to use this option without the -l option. When used together with the -u option, the server socket is not connected and it can receive UDP datagrams from multiple hosts.
Funny. One would expect that the -u option also implies -k, since UDP is a connection-less protocol.
The -w option is needed both in the server and the client side.
Also, the Debian netcat version 1.130-3 has a bug when using both options -u and -v. This bug is fixed in version 1.178-3.
And, what about those leading XX characters in the server output?
So, after spending some of my time on netcat, I decided to drop it and use socat.
The server is now:
$ socat - udp4-listen:9876,reuseaddr,fork
And the client is now:
$ socat - udp-sendto:127.0.0.1:9876
Simple and beautiful :)
I can even kill the client, re-spawn it, and still get the messages to appear in the server!