Packet capture: tcpdump, tshark, Wireshark and pktmon
ipconfig tells you what your computer thinks its configuration is. A packet capture shows what is actually on the wire: whether the DHCP server answered, whether DNS queries leave, whether the TCP handshake completes. It is the final word in most arguments about "whose fault is it".
tcpdump (Linux, macOS, BSD)
Installed by default on macOS and most Linux servers (apt install tcpdump otherwise). Needs root or the cap_net_raw capability. On macOS 14 and later you are asked to allow Terminal to capture the first time.
sudo tcpdump -i eth0 # everything on eth0, names resolved, until Ctrl+C
sudo tcpdump -i eth0 -n # -n: no name resolution (always use it; much faster and clearer)
sudo tcpdump -i eth0 -nn # -nn: also show port numbers instead of service names
sudo tcpdump -i any -n # all interfaces (Linux)
sudo tcpdump -D # list interfaces
sudo tcpdump -i eth0 -n -c 20 # stop after 20 packets
sudo tcpdump -i eth0 -n -v # more detail (-vv, -vvv for more)
sudo tcpdump -i eth0 -n -e # include MAC addresses
sudo tcpdump -i eth0 -n -X # hex and ASCII dump of each packet
sudo tcpdump -i eth0 -n -A # ASCII payload (readable for HTTP, DNS names)
sudo tcpdump -i eth0 -n -tttt # full timestamps
sudo tcpdump -i eth0 -n -s 0 -w capture.pcap # save everything to a file for Wireshark
sudo tcpdump -i eth0 -n -w capture.pcap -c 1000
sudo tcpdump -i eth0 -n -w cap.pcap -G 60 -W 10 # rotate files every 60 s, keep 10
tcpdump -n -r capture.pcap # read a file back
tcpdump -n -r capture.pcap 'port 53' # read with a filter
sudo tcpdump -i eth0 -n -l | tee live.txt # line-buffered for pipingCapture filters (BPF syntax)
The filter goes at the end, in quotes. It is applied in the kernel, so a narrow filter keeps the load down and the output readable.
sudo tcpdump -i eth0 -n host 192.168.1.10 # to or from one host
sudo tcpdump -i eth0 -n src 192.168.1.10
sudo tcpdump -i eth0 -n dst 1.1.1.1
sudo tcpdump -i eth0 -n net 192.168.1.0/24
sudo tcpdump -i eth0 -n port 53 # DNS
sudo tcpdump -i eth0 -n 'port 67 or port 68' # DHCP
sudo tcpdump -i eth0 -n arp # ARP only
sudo tcpdump -i eth0 -n icmp # ping and errors
sudo tcpdump -i eth0 -n icmp6 # IPv6 neighbour discovery, RAs
sudo tcpdump -i eth0 -n tcp port 443
sudo tcpdump -i eth0 -n 'tcp port 80 and host 192.168.1.10'
sudo tcpdump -i eth0 -n 'not port 22' # everything except your SSH session
sudo tcpdump -i eth0 -n 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0' # new connection attempts (SYN only)
sudo tcpdump -i eth0 -n 'tcp[tcpflags] & (tcp-rst) != 0' # resets
sudo tcpdump -i eth0 -n 'udp and not port 53'
sudo tcpdump -i eth0 -n 'ether host d4:6e:0e:11:22:33' # by MAC
sudo tcpdump -i eth0 -n 'vlan 10'
sudo tcpdump -i eth0 -n 'ip6'
sudo tcpdump -i eth0 -n 'greater 1400' # packets over 1400 bytes (MTU hunting)Reading the output
12:04:31.118342 IP 192.168.1.42.52114 > 142.250.187.196.443: Flags [S], seq 3919253911, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0
12:04:31.127905 IP 142.250.187.196.443 > 192.168.1.42.52114: Flags [S.], seq 1122334455, ack 3919253912, win 65535, options [mss 1412,sackOK,TS val 2 ecr 1,nop,wscale 8], length 0
12:04:31.127950 IP 192.168.1.42.52114 > 142.250.187.196.443: Flags [.], ack 1, win 502, length 0
12:04:31.130001 IP 192.168.1.42.52114 > 142.250.187.196.443: Flags [P.], seq 1:518, ack 1, win 502, length 517- Flags [S], [S.], [.], [P.], [F.], [R]
- SYN, SYN-ACK (the dot is ACK), ACK, PSH-ACK (data), FIN-ACK, RST. The first three lines above are a complete handshake; a SYN with no [S.] reply repeated every second is a blocked or unreachable port; a [R] straight back is a closed port.
- 192.168.1.42.52114 > 142.250.187.196.443
- Source address.port > destination address.port. With
-nn. Without it, 443 shows as https. - mss 1460 / mss 1412
- Each side's maximum segment size. The far side's 1412 reveals a 1452-byte MTU somewhere (PPPoE plus something). Useful for MTU problems.
- length
- Payload bytes.
Recipes
Is the DHCP server answering?
sudo tcpdump -i eth0 -n -v 'port 67 or port 68'
# then in another terminal: sudo dhclient -r eth0; sudo dhclient eth0 (or ipconfig /renew on the client you are testing)
# healthy: Discover, Offer, Request, ACK within a second. Only Discover/Request repeating: no server is hearing you.Where do my DNS queries go, and do they come back?
sudo tcpdump -i eth0 -n 'port 53'
# then: dig example.com
# you should see A? example.com. to your resolver and a reply with the address. Query with no reply: resolver dead or blocked. No query at all: the cache answered, or the browser is using DoH (port 443).Who is sending ARP for the gateway, and who answers?
sudo tcpdump -i eth0 -n -e arp
# two different MACs answering 'is-at' for 192.168.1.1 = duplicate IP or ARP spoofingIs my port actually reachable from outside?
sudo tcpdump -i eth0 -n 'tcp port 8080'
# then connect from another machine. SYN arriving but no SYN-ACK: nothing is listening or the host firewall drops it. Nothing arriving: the router/NAT or upstream firewall is blocking.What is this device talking to?
sudo tcpdump -i eth0 -n 'host 192.168.1.88 and not arp' -c 200 | awk '{print $5}' | cut -d. -f1-4 | sort | uniq -c | sort -rn | head
# needs the traffic to pass your capture point: run it on the router (OpenWrt has tcpdump), or use a switch mirror portIPv6 router advertisements
sudo tcpdump -i eth0 -n -v 'icmp6 and ip6[40] == 134' # RA packets: prefix, lifetime, M/O flags, RDNSSWireshark and tshark
Wireshark is the graphical analyser; tshark is the same engine in the terminal. Both read pcap files from tcpdump and pktmon. Wireshark's display filters use a different syntax from tcpdump's capture filters:
| Want | tcpdump capture filter | Wireshark display filter |
|---|---|---|
| One host | host 192.168.1.10 | ip.addr == 192.168.1.10 |
| DNS | port 53 | dns |
| DHCP | port 67 or port 68 | dhcp (older: bootp) |
| HTTP requests | tcp port 80 | http.request |
| TLS handshakes with server name | tcp port 443 | tls.handshake.extensions_server_name |
| SYN without ACK | tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn | tcp.flags.syn == 1 && tcp.flags.ack == 0 |
| Retransmissions | n/a | tcp.analysis.retransmission |
| Errors of all kinds | n/a | tcp.analysis.flags || icmp.type == 3 |
| Slow responses | n/a | http.time > 1, dns.time > 0.5 |
| Exclude your SSH session | not port 22 | !(tcp.port == 22) |
tshark -i eth0 -n -f 'port 53' -Y 'dns.flags.response == 1' -T fields -e dns.qry.name -e dns.a # names and answers only
tshark -r capture.pcap -q -z conv,ip # conversation summary per IP pair
tshark -r capture.pcap -q -z io,phs # protocol hierarchy
tshark -r capture.pcap -Y 'tcp.analysis.retransmission' | wc -l
tshark -r capture.pcap -T fields -e frame.time -e ip.src -e ip.dst -e _ws.col.Info | head
tshark -i eth0 -a duration:60 -w one-minute.pcapIn Wireshark, Statistics › Conversations, Statistics › Protocol Hierarchy, Analyze › Expert Information and right-click › Follow › TCP Stream answer most questions without writing a filter. The Time column can be switched to "seconds since previous displayed packet" to spot delays.
Windows: pktmon and netsh trace
Windows 10 1809 and later include pktmon, which captures at every layer of the stack (so you can see a packet arrive at the NIC and vanish in a filter driver). Wireshark for Windows uses Npcap and is the friendlier option when you can install software.
:: capture DNS and DHCP to an ETL file, then convert for Wireshark (admin)
pktmon filter add -p 53
pktmon filter add -p 67
pktmon filter add -p 68
pktmon start --capture --pkt-size 0
:: ...reproduce the problem...
pktmon stop
pktmon etl2pcapng PktMon.etl -o capture.pcapng
pktmon filter remove
:: live text output instead of a file (1809-21H1 syntax differs slightly)
pktmon start --capture --log-mode real-time
:: list interfaces and components
pktmon list
pktmon list --all
:: filter examples
pktmon filter add -i 192.168.1.10
pktmon filter add -t tcp -p 443
pktmon filter add -m 8C-16-45-3A-9B-2D
pktmon filter add -t arp
:: the older way: netsh trace, also ETL, convert with etl2pcapng from GitHub
netsh trace start capture=yes tracefile=C:\temp\net.etl maxsize=200
netsh trace stopPowerShell 5.1+ also has New-NetEventSession/Add-NetEventPacketCaptureProvider for scripted captures. In Wireshark on Windows, capturing on Wi-Fi shows only your own traffic (no monitor mode with most drivers); Ethernet shows the same broadcast and multicast traffic as any other host.
Capturing where the traffic actually is
- On a switched network you only see your own traffic plus broadcasts and multicasts (ARP, DHCP, mDNS, SSDP). To see another device's traffic you need a mirror/SPAN port on a managed switch, a hub or a tap, or to capture on the router.
- Routers: OpenWrt (
opkg install tcpdump), pfSense/OPNsense (Diagnostics › Packet Capture), UniFi (SSH thentcpdump), MikroTik (/tool sniffer), Fritz!Box (fritz.box/html/capture.html). Stream a router capture to Wireshark:ssh root@router tcpdump -i br-lan -n -w - | wireshark -k -i -. - Wi-Fi monitor mode captures every frame in the air on one channel, including other devices' (encrypted) traffic and all management frames. Linux:
iw dev wlan0 set type monitororairmon-ng; macOS: Wireshark's monitor-mode checkbox ortcpdump -I. Windows: generally not supported. - Inside a container or namespace:
nsenter -t PID -n tcpdump -i eth0, or capture on the host's veth/bridge.
Related pages
- How DHCP works (with an annotated capture)
- Troubleshooting playbook
- netstat and ss: what is connected