tcpdump: how to get grepable output?

Posted on

Problem :

I am trying to troubleshoot an issue where I only have tcpdump available on an appliance.
I want to use tcpdump to filter web traffic, and only display traffic containing certain strings.

I do the following:

tcpdump -nei eth0 -X | grep "something interesting"

The output is a hexview with 16 bytes pr line. I cannot grep this data, as the data is presented on multiple lines.

Is there a way for tcpdump to present the captured data on a single line? This would make it possible to use grep to find interesting packets.

Solution :

For those like you who cannot use ngrep, here’s how to use awk to make the tcpdump output of packet contents grepable.

First some sample output as provided by tcpdump -x, in order to present the task ahead:

$ tcpdump -xr dump.pcap 2>/dev/null
12:04:59.590664 IP 10.17.14.93.51009 > 239.194.1.9.51009: UDP, length 370
        0x0000:  4500 018e 0000 4000 fa11 7625 0a11 0e5d
        0x0010:  efc2 0109 c741 c741 017a 6f28 1120 2020
        0x0020:  3337 3030 3039 3031 3835 3635 3430 3130
...

And this is the copy-and-pastable awk script you can pipe the output to

awk '{ if (match($0, /^[0-9]/, _)) { printf (NR == 1 ? "%s " : "n%s "), $0; fflush() } else { sub(/^s+0x[0-9a-z]+:s+/, " "); gsub(" ", ""); printf "%s", $0 } } END { print ""; fflush() }'

in order to get the following, grepable output

12:04:59.590664 IP 10.17.14.93.51009 > 239.194.1.9.51009: UDP, length 370 4500018e00004000fa1176250a...
12:04:59.590798 IP 10.17.14.113.51011 > 239.194.1.11.51011: UDP, length 370 4500018e00004000fa11760f...
...

Below is a commented version of above script:

awk '{
    # if this is a header line
    if (match($0, /^[0-9]/, _)) {
        # print the header, but:

        # except for the first line,
        # we need to insert a newline,
        # as the preceding data lines will
        # have been stripped of theirs

        # we also append a space to
        # separate header info from the
        # data that will get appended
        printf (NR == 1 ? "%s " : "n%s "), $0
        # enforce line-buffering
        fflush()
    }
    # otherwise it is a data line
    else {
        # remove the data address
        sub(/^s+0x[0-9a-z]+:s+/, " ");
        # remove all spaces
        gsub(" ", "");
        # print w/o newline
        printf "%s", $0 
    }
}
END {
    # print final newline, as
    # the preceding data lines will
    # have been stripped of theirs
    print ""
    # enforce line-buffering
    fflush()
}'

From the tcpdump manpage:

-A      Print each packet (minus its link level header) in ASCII.  Handy
        for capturing web pages.

Make sure you also use the -s 0 option to make sure the entire packet is displayed.

You may want to have a look at ngrep command:

ngrep -W single -d eth0 'regex to match' 'port 80'

Where:

  • -W single specifies single line formatting
  • regex to match means to only dump packets containing certain string.
  • 'port 80' is a pcap filter to only sniff packets from or to port 80

tcpdump -nei eth0 -l | grep "something interesting"

man 1 tcpdump: “-l Make stdout line buffered. Useful if you want to see the data while capturing it.”

Leave a Reply

Your email address will not be published. Required fields are marked *