Monday, July 1, 2024

Automating tcpdump in Test Scripts

 It's not unusual for me to create a shell script to test networking software, and to have it automatically run tcpdump in the background while the test runs. Generally I do this "just in case something gets weird," so I usually don't pay much attention to the capture.

The other day I was testing my new "raw_send" tool, and my test script consisted of:

INTFC=em1
tcpdump -i $INTFC -w /tmp/raw_send.pcap &
TCPDUMP_PID=$!
sleep 0.2
./raw_send $INTFC \
 01005e000016000f53797050080046c00028000040000102f5770a1d0465e0000016940400002200e78d0000000104000000ef65030b
sleep 0.2
kill $TCPDUMP_PID

Lo and behold, the tcpdump did NOT contain my packet! I won't embarrass myself by outlining the DAY AND A HALF I spent figuring out what was going on, so I'll just give the answer.

The tcpdump tool wants to be as efficient as possible, so it buffers the packets being written to the output file. This is important because a few large file writes are MUCH more efficient than many small file writes. When you kill the tcpdump (either with the "kill" command or with control-C), it does NOT flush out the current partial buffer. There was a small clue provided in the form of the following output:

tcpdump: listening on em1, link-type EN10MB (Ethernet), capture size 262144 bytes
0 packets captured
1 packets received by filter
0 packets dropped by kernel

I thought it was filtering out my packet for some reason. But no, the "0 packets captured" means that zero packets were written to the capture file ... because of buffering.

The solution? Add the option "--immediate-mode" to tcpdump:

tcpdump -i $INTFC -w /tmp/raw_send.pcap --immediate-mode &

Works fine.


Python and Perl and Bash (oh my!)

I've been thinking a lot recently about code maintainability, including scripts. I write a lot of shell scripts, usually restricting myself to Bourne shell features, not GNU Bash extensions. I also write a lot of Perl scripts, mostly because I'm a thousand years old, and back then, Perl was the state of the scripting art.

Anyway, it's not unusual for me to write a shell script that invokes Perl to do some regular expression magic. For example, I recently wanted to take a dotted IP address (e.g. "10.29.3.4") and convert it into a string of 8 hexadecimal digits representing the network order binary representation of the same (i.e. "0a1d0304"). My kneejerk reaction is this:

HEX=`echo "10.29.3.4" | perl -nle 'if (/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) { printf("%02x%02x%02x%02x\n", $1, $2, $3, $4) } else {die "invalid IP $_\n"'}`

But since maintainability has been on my mind lately, most programmers (especially younger ones) would have a steep Perl learning curve to maintain that. So my second thought was to do it in Bash directly. I haven't done much regular expression processing in Bash, given my habit of staying within Bourne, but really, that habit has outlived its usefulness. Open-source Unix (Linux and Berkley) have relegated other Unixes to rare niche use cases, and even those other Unixes have a way to download Bash. I should just accept that Bash extensions are fair game. Here's my second try:

pattern='^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$'
if [[ "$MCAST_ADDR" =~ $pattern ]]; then
  GRP_HEX=$(printf '%02x%02x%02x%02x' ${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]})
else echo "invalid IP addr"; exit 1
fi

But even as I feel fairly confident that more programmers would be able to maintain that than the Perl version, I still realize that the vast majority of programmers I've known have very basic shell scripting skills. I'm not sure the Bash version expands the pool of qualified maintainers by much. I think the best that can be said is that a programmer with basic shell scripting skills can learn regular expression matching in Bash a lot faster than learning enough Perl to do it.

So, how about Python?

Well, with some help from Claude, here's a Python one-liner:

HEX=`python3 -c "import sys, ipaddress; print('{:08x}'.format(int(ipaddress.ip_address(sys.argv[1]))))" 10.29.2.3`

So, not only does this use a language that many programmers know, it also completely avoids regular expressions, which is another uncommon skill among the programmers I've known.

*sigh*

What's a dinosaur to do? I haven't been paid to be a programmer for a lot of years, so the programming I do is mostly for my own entertainment. And dammit, I *like* Perl! I've done enough Python programming to know that ... I just don't like it that much. And let's face it: the code I write is unlikely to be widely used, so who cares about maintainability?

(pause for effect)

I do.

I have standards on how programming should be done. If programming is now largely my hobby, I get the most personal reward by doing it according to my standards. I think it's time for me to say a fond farewell to Perl and bow down to my Python overlords.