This program is the workhorse for capturing network packets.

Note
If you need to capture traffic between two hosts that you don’t have easy ability to run software on but you have physical access, interject an old school hub between the hosts and put a 3rd host with decent Linux tools (running the capture) on that hub too.
Note
tcpdump pretty much always likes you more if your UID is 0 (i.e. use sudo and run as root). If you can’t do this, see trick above.

Useful Options

  • -U Flush on each packet instead of the arbitrary point when the buffer fills.

  • -w <file|-> Write packets to a standard pcap file instead of parsing them and printing summary information about them. Use - for standard output.

  • -r <file>|- Read the packets from a file (maybe previously collected with -w) instead of a network source.

  • -i <interface> Specify the interface to listen to (eth0, etc.).

  • -s <val|0> Snapshot length is number of bytes per packet to look at. The default is 68 which may be enough to analyze the nature of traffic, but not enough to analyze the content of the packet. Set to 0 to capture the whole packet.

  • -tttt Print timestamp on each line of the dump.

  • -X Include contents of the payload. Use -XX to include every thing including header data. If you just want the ASCII part try the similar -A.

  • -n is don’t resolve names. -nn is don’t resolve names or ports. These are faster and involve less network clutter.

  • -l relates to line buffering. It basically flushes the output after every line. If you want the output dispatched after every packet try the -U option. One of these options is important if you’re expecting sensible real-time activity from Unix pipelines.

BPF

Once you’ve set the options for how tcpdump will work, specifying exactly what it will do is a huge topic. The rich syntax may seem bewildering and overly complex at first, but it is part of an ancient and proven technology called BPF. This is actually a full byte-code language that gets compiled (yes, within the execution of tcpdump).

To find out a lot about the immense functionality in this syntax check out man 7 pcap-filter.

Decoding Output

The general format of a tcp protocol line is:

src > dst: flags data-seqno ack window urgent options
Table 1. TCP Flags

U

URG

Urgent

.

ACK

Acknowledge (bit 11, don’t know its tcpdump code)

P

PUSH

R

RST

Reset

S

SYN

Synthesize (i.e. initiate)

F

FIN

E

ECN-Echo

Explicit Congestion Notification (RFC3168)

W

ECN CWR

Congestion Window Reduced

For the canonical reference see RFC793 - Transmission Control Protocol.

From the man page comes this helpful explanation.

Src and dst are the source and destination IP addresses and ports. Flags are some combination of S (SYN), F (FIN), P (PUSH), R (RST), U (URG), W (ECN CWR), E (ECN-Echo) or . (ACK), or none if no flags are set. Data-seqno describes the portion of sequence space covered by the data in this packet (see example below). Ack is sequence number of the next data expected the other direction on this connection. Window is the number of bytes of receive buffer space available the other direction on this connection. Urg indicates there is urgent data in the packet. Options are tcp options enclosed in angle brackets (e.g., <mss 1024>).

Tips

Pipe To Wireshark

By using a file name option of -w- the pcap file will be written to standard output. It seems that you can then pipe that to the Wireshark executable.

Dumping to Files

By default the packets are summarized and cleaned up and sent to stdout, but you can specify a file name for the actual raw packets captured. This file can then be used by tcpdump in the place of actual interfaces to see the summarized and cleaned up output. Writing:

# tcpdump -w /thenameofthefilehere.pcap

Reading (perhaps to have a quick look at it):

# tcpdump -r /thenameofthefilehere.pcap | less

Snapshot Length

This controls the snapshot length which on Linux seems to be 90 bytes even though the man page says 68. This means that it only thinks about and writes the first 90 bytes of each packet. That’s fine if you just want to see packet headers and get a sense of what’s going on, but if you want to guarantee that a particular content is or isn’t (e.g. clear text passwords) being transmitted, you might want to capture all bytes. Set this to 0 for all bytes in the packet to be captured. Example:

# tcpdump -i eth0 -s 0

Focusing On The Problem

Let’s say you’re trying to troubleshoot some particular problem. If, for example, you want to scrutinize a web page request, you want to focus on port 80. Here’s how:

$ sudo tcpdump -i eth0 -s 0 tcp port 80

Or just look at the traffic to and from a specific host.

$ sudo tcpdump -i wlan0 host 192.168.1.243

Or more than one host.

$ sudo tcpdump -i enp3s0  'host 192.168.1.253 or host 192.168.1.4'

Sometimes there is a bunch of chatter from chattery processes and equipment (routers are filled with STP - spanning tree protocol, ARP - address resolution protocol, DTP - dynamic trunking protocol, etc). Perhaps the worst offender for traffic you do not wish to keep an eye on is the SSH traffic that you’re using to log into the machine in the first place. To specifically exclude such unwanted traffic from consideration use something like this:

tcpdump -i eth0 -s 0 not tcp port 22 and not tcp port 53 and not tcp port 123 and not arp and not stp

Breaking Up Huge Capture Files

Another way to focus on the problem involves looking for a needle in a haystack with respect to time. What if you have a giant capture file with gigabytes of packets? Maybe you want to start searching about 80% through the pcap file. You can use tcpdump itself to break up large pcap file.

This command will break up the large radar data file into smaller files of (roughly) 100 MB.

tcpdump -r radar_capture-191028-115136.pcap -w riversections.pcap -C 100

This will produce a lot of files like riversections.pcap1 etc. Note that they don’t sort well since they’re not zero padded.

This lets me check a small file and if it’s in the wrong region, I can skip ahead a few files and zoom in on my region of interest.

Analyzing A Specific Type Of Traffic

In this example I’m trying to capture all of the packets associated with a secure LDAP connection. By capturing all of the data that was passed back and forth to the LDAP server, I can search through it and check to see if any passwords went over in the clear. First to get the data use this command:

sudo tcpdump -i eth0 -s 0 -w ldapcapture.pcap tcp port 636

Then run wireshark ldapcapture.pcap to start up the graphical analysis program Wireshark. Go to Edit->Find Packet (Ctrl-F) and do a string search for the password you’re looking for (or hoping you won’t find).

Skip Resolving

To prevent hostname lookups use -n. What I find really annoying, however, are the often incorrect port/service lookups. To prevent this use -nn. This means that if you want hosts you get services too as far as I can tell.

Over SSH Without The SSH Traffic

Often you’ll need to monitor something on another computer. Obviously you monitoring is traffic and that is not interesting. Furthermore, it will become a feedback loop - the more traffic you monitor, the more traffic there is. To get around this problem you need to filter yourself out of what is being watched.

sudo /usr/sbin/tcpdump -i eth0 port not 22

Or if you want to watch the SSH traffic of other processes, you can try something like this.

sudo /usr/sbin/tcpdump -i eth0 port not 22 and host 192.168.0.88

Permissions Problems

It can be very frustrating and annoying to need sudo for every packet capture. For most modern Linux applications this is not a security risk (or it shouldn’t be!). There are rumors of Wireshark having an install mode where normal users can capture packets. Here is a guy who has explicitly figured out how to change Linux capabilities and permissions to accomplish this.

When you install Wireshark in Debian it asks if you want normal users to be able to capture packets. I just tried that and answered yes and — guess what? — still can’t capture packets. I even added my user to the wireshark group which was created during the install.

gpasswd -a xed wireshark

Useful Examples

VLAN Discovery

Before jumping into tcpdump, it can be helpful to check if the link is even properly up.

sudo ethtool eth0

Once the link is known to be active, one might wonder if the correct VLAN is active on the network.

sudo tcpdump -nn -v -i eth0 -s 1500 -c 1 'ether[20:2] == 0x2000'

Checking For Passwords In The Clear

Are you at some login page and HTTPS is not active and you are wondering if your plain text password can be sniffed off the wire? Well, here’s how you sniff it to check.

:-<[dev-desktop][~]$ sudo tcpdump -s 0 -A -n -l | egrep -i "os_password"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp3s0, link-type EN10MB (Ethernet), capture size 262144 bytes
os_username=testing&os_password=THISPASSWORDSENTINTHECLEAR&login=Log+in&os_destination=%2Findex.action

In this case I used os_password because tests showed that’s what it was. But if you’re not sure, grep out something like this "POST /|pwd=|passwd=|password=|Host:".

Analyzing Bandwidth

Let’s say you want to watch a connection and see what activities cause a big spike in network traffic. Here’s what I used to do that.

# tcpdump -U -s 0 -i eth0 -w - host example.xed.ch  | ./bytecounter

The bytecounter program is a simple C program that spits out the current byte count as it is piped in:

bytecounter.c
#include  <stdio.h>
int main(int argc, char * argv[]) {
    int c, n=0;
    while ((c= getchar()) != EOF) {
        n++;
        if (n%100==0) {
            printf("\r<%d>",n);
            fflush(stdout);
        }
    }
}

Simple Text Data

To analyze the communication between bots and the simulation in the Simulated Car Racing competition, I needed to eavesdrop on simple data transmitted locally over UDP. Here’s what worked well:

sudo tcpdump -A -nn -i lo -s 0 udp port 3010  # All traffic
sudo tcpdump -A -nn -i lo -s 0 udp dst port 3010  # Driver Inputs
sudo tcpdump -A -nn -i lo -s 0 udp src port 3010  # Car Feedback

Finding MAC Addresses

This technique is helpful when you’re dealing with devices who only reveal their MAC addresses to the network. An example is a Sony PlayStation3. If you have MAC address filtering on your router, this is how you can find out what that MAC address is.

Another good case for this technique is if you just installed a new cluster and you plugged everything in. You’d like to give the machines a DHCP lease based on MAC address but you don’t know the MAC address. You could power up each machine with a monitor plugged in and write it down as it POSTs. But that’s lame. Better to just power it up fresh and let its natural factory-set desire to get it’s OS with PXE do the work. Basically you’ll want to use another working machine on that network and watch as you power up each node while tcpdump records the nodes' futile attempts to find out who it is.

# tcpdump -tttt -i eth1 port bootps or port bootpc | tee MACs.dump

The -tttt is needed to timestamp packets so you can match it with when you power on the machines. If you power on the machines quicker than its PXE cycle then there will be confusion unless precautions are taken. Turn them on at 15 second intervals using a watch for accuracy and look for that first packet at that interval.

Programming With libpcap

tcpdump is a brilliant program that does everything you could ever want, but still, you might get into a situation where you only need a very specific thing. To write your own special purpose program that can analyze network traffic you should use the same thing tcpdump uses, libpcap.

Here is a nice practical guide to libpcap.

Make sure you have Debian packages libpcap-dev and/or maybe libpcap0.8-dev. Also package pcaputils will contain pcapdump. And make sure you don’t forget to include the gcc -lpcap option.

Here is a sample program based on the documentation found at http://www.tcpdump.org/pcap.html .

/* Chris X Edwards - 2018-11-18 */
/* Compile: gcc -lpcap -o pcap_sample pcap_sample.c*/

#include <pcap.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#define MAXBYTES2CAPTURE 2048

/* Callback specifying what to do for each packet captured. */
void processPacket(u_char *userarg, const struct pcap_pkthdr* pkthdr, const u_char * packet){
    int i= 0, *counter= (int *)userarg;
    printf("Packet Count: %d\n", ++(*counter));
    printf("Received Packet Size: %d\n", pkthdr->len);
    printf("Payload:\n");
    for (i=0;i<pkthdr->len;i++) {
        if (isprint(packet[i]))     /* If it's printable... */
            printf("%c",packet[i]); /* ...print it. */
        else
            printf(".");           /* Dots if not printable. */

        if ( (i%64 == 0 && i !=0) || i == pkthdr->len-1)
            printf("\n");
    }
    return;
}

int main(){
    int count=0;
    pcap_t *pkthandle= NULL;
    char errbuf[PCAP_ERRBUF_SIZE], *device=NULL;
    memset(errbuf,0,PCAP_ERRBUF_SIZE);
    struct bpf_program myfilter;
    char filter_text[]= "host 192.168.1.1";
    bpf_u_int32 netmask;       /* The netmask of our sniffing device */

    /* Get the name of the first device suitable for capture. */
    device= pcap_lookupdev(errbuf);
    printf("Opening device %s\n", device);

    /* Open device in promiscuous mode. */
    pkthandle= pcap_open_live(device, MAXBYTES2CAPTURE, 1, 512, errbuf);
    /* Warnings and non-sudo errors, etc. */
    if (strlen(errbuf)) {printf("%s\n",errbuf);exit(1);}

    if (pcap_compile(pkthandle, &myfilter, filter_text, 0, netmask) == -1) {
        fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_text, pcap_geterr(pkthandle));
            return(2);
    }
    if (pcap_setfilter(pkthandle, &myfilter) == -1) {
        fprintf(stderr, "Couldn't install filter %s: %s\n", filter_text, pcap_geterr(pkthandle));
        return(2);
    }
    /* Loop forever (cnt=-1) & call processPacket() for every received packet. */
    pcap_loop(pkthandle, -1, processPacket, (u_char *) &count);
    return 0;
}

This program will look for the first sniffable network and then start looking for packets that pass the filter. The filter is defined in filter_text and just needs to match traffic involving the host 192.168.1.1. Once it finds such a packet, it passes it to the processPacket() callback where it is printed.

Using Packet Capture Files

You’re playing with some cool expensive toy that spits out network traffic but you can’t hog this device while you spend days debugging your software. Or you’re trying to prepare a response to a network traffic event that happens only very rarely. The trick is to record some of this traffic and later use these recordings offline during testing and development.

The libpcap is really good about making this easy. This worked for me.

#define FROM_PCAP_FILE
#ifdef FROM_PCAP_FILE
char* filename= "/tmp/pcap/device_in_office_testing.pcap";
pkthandle= pcap_open_offline(filename, errbuf);
#else
/* Get the name of the first device suitable for capture. */
device= pcap_lookupdev(errbuf);
printf("Opening device %s\n", device);
/* Open device in promiscuous mode (3rd arg). */
pkthandle= pcap_open_live(device, MAXBYTES2CAPTURE, 1, 512, errbuf);
#endif

When you’re ready to try out your code on real live data from the device you’re working on, just deactivate the #define.