Chapter 11: Network Monitoring and Packet Analysis


Seeing What’s on the Wire

Debugging network issues, analyzing protocols, and testing security all require the ability to capture and inspect the raw packets flowing through your network. This chapter covers the essential tools for packet analysis — from command-line tcpdump to the powerful Python library Scapy — and shows you how to build custom monitoring solutions.


tcpdump — Command-Line Packet Capture

tcpdump is the foundational packet capture tool available on virtually every Unix system.

Basic Captures

# Capture all traffic on eth0
sudo tcpdump -i eth0

# Capture with verbose output
sudo tcpdump -i eth0 -v

# Capture and save to a file (pcap format)
sudo tcpdump -i eth0 -w capture.pcap

# Read a saved capture
tcpdump -r capture.pcap

Filtering Traffic

BPF (Berkeley Packet Filter) expressions let you focus on specific traffic:

# Only TCP traffic on port 80
sudo tcpdump -i eth0 'tcp port 80'

# Only traffic to/from a specific host
sudo tcpdump -i eth0 'host 192.168.1.42'

# Only DNS queries (UDP port 53)
sudo tcpdump -i eth0 'udp port 53'

# SYN packets only (new TCP connections)
sudo tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0'

# Traffic between two hosts
sudo tcpdump -i eth0 'host 10.0.0.1 and host 10.0.0.2'

# HTTP requests (look for GET/POST in payload)
sudo tcpdump -i eth0 -A 'tcp port 80' | grep -E '^(GET|POST|HTTP)'

Useful tcpdump Options

Option Description
-i <iface> Capture on specific interface (any for all)
-c <count> Stop after capturing N packets
-w <file> Write packets to pcap file
-r <file> Read from pcap file
-n Don’t resolve hostnames (faster)
-nn Don’t resolve hostnames or ports
-v, -vv, -vvv Increasing verbosity
-A Print packet payload as ASCII
-X Print payload as hex and ASCII
-s <size> Capture size (0 = full packet)

Wireshark and tshark

Wireshark is the graphical packet analyzer. Its CLI counterpart, tshark, is useful for scripting:

# Capture with tshark
sudo tshark -i eth0 -f 'tcp port 443' -c 100

# Read a pcap file and display HTTP requests
tshark -r capture.pcap -Y 'http.request' -T fields -e http.host -e http.request.uri

# Extract TLS handshake info
tshark -r capture.pcap -Y 'tls.handshake' -T fields -e tls.handshake.type -e tls.handshake.extensions_server_name

# Statistics: protocol hierarchy
tshark -r capture.pcap -q -z io,phs

Scapy — Packet Crafting and Analysis in Python

Scapy is a powerful Python library that lets you craft, send, sniff, and analyze network packets at any layer.

Installation

pip install scapy

Most Scapy operations require root privileges (raw socket access).

Crafting Packets

Scapy uses a layered syntax that mirrors the protocol stack:

from scapy.all import IP, TCP, UDP, ICMP, Ether, Raw

# Create an ICMP echo request (ping)
pkt = IP(dst="8.8.8.8") / ICMP()

# Create a TCP SYN packet
syn = IP(dst="example.com") / TCP(dport=80, flags="S")

# Create a full HTTP request
http_req = (
    IP(dst="example.com")
    / TCP(dport=80, flags="PA")
    / Raw(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
)

# Inspect a packet
syn.show()
print(syn.summary())

Sending and Receiving

from scapy.all import IP, ICMP, sr1, send

# Send and receive one response (like ping)
response = sr1(IP(dst="8.8.8.8") / ICMP(), timeout=2)
if response:
    print(f"Reply from {response.src}: TTL={response.ttl}")

# Send without waiting for a response
send(IP(dst="8.8.8.8") / ICMP(), count=3)

Sniffing Packets

from scapy.all import sniff, IP, TCP

def packet_handler(pkt):
    if IP in pkt and TCP in pkt:
        print(f"{pkt[IP].src}:{pkt[TCP].sport}{pkt[IP].dst}:{pkt[TCP].dport} "
              f"flags={pkt[TCP].flags}")

# Sniff 20 TCP packets
sniff(filter="tcp", prn=packet_handler, count=20, iface="eth0")

Full example: code/scapy_sniffer.py

Traceroute with Scapy

from scapy.all import IP, ICMP, sr1

def traceroute(target: str, max_hops: int = 30):
    print(f"Traceroute to {target}")
    for ttl in range(1, max_hops + 1):
        pkt = IP(dst=target, ttl=ttl) / ICMP()
        reply = sr1(pkt, timeout=2, verbose=0)
        if reply is None:
            print(f"{ttl:3d}  * * *")
        elif reply.type == 0:  # Echo reply — destination reached
            print(f"{ttl:3d}  {reply.src} (reached)")
            break
        else:  # TTL exceeded
            print(f"{ttl:3d}  {reply.src}")

traceroute("8.8.8.8")

Port Scanner

from scapy.all import IP, TCP, sr1

def scan_port(target: str, port: int) -> str:
    """Scan a single TCP port using SYN scan."""
    pkt = IP(dst=target) / TCP(dport=port, flags="S")
    resp = sr1(pkt, timeout=1, verbose=0)
    if resp is None:
        return "filtered"
    if resp.haslayer(TCP):
        if resp[TCP].flags == "SA":  # SYN-ACK
            # Send RST to close
            sr1(IP(dst=target) / TCP(dport=port, flags="R"), timeout=1, verbose=0)
            return "open"
        elif resp[TCP].flags == "RA":  # RST-ACK
            return "closed"
    return "unknown"

# Scan common ports
for port in [22, 80, 443, 8080, 3306]:
    status = scan_port("192.168.1.1", port)
    print(f"Port {port:5d}: {status}")

Full example: code/port_scanner.py


Analyzing Pcap Files

Reading Pcap Files

from scapy.all import rdpcap, IP, TCP

packets = rdpcap("capture.pcap")
print(f"Total packets: {len(packets)}")

# Filter and analyze
tcp_packets = [p for p in packets if TCP in p]
for pkt in tcp_packets[:10]:
    print(f"{pkt[IP].src}:{pkt[TCP].sport}{pkt[IP].dst}:{pkt[TCP].dport}")

Protocol Statistics

from scapy.all import rdpcap, IP, TCP, UDP, ICMP
from collections import Counter

packets = rdpcap("capture.pcap")

protocols = Counter()
for pkt in packets:
    if TCP in pkt:
        protocols["TCP"] += 1
    elif UDP in pkt:
        protocols["UDP"] += 1
    elif ICMP in pkt:
        protocols["ICMP"] += 1
    else:
        protocols["Other"] += 1

for proto, count in protocols.most_common():
    print(f"{proto:10s}: {count}")

Building a Network Monitor

Let’s build a simple real-time bandwidth monitor:

import time
from collections import defaultdict
from scapy.all import sniff, IP

traffic = defaultdict(lambda: {"bytes_in": 0, "bytes_out": 0, "packets": 0})
MY_IP = "192.168.1.42"

def monitor(pkt):
    if IP in pkt:
        size = len(pkt)
        if pkt[IP].dst == MY_IP:
            traffic[pkt[IP].src]["bytes_in"] += size
        elif pkt[IP].src == MY_IP:
            traffic[pkt[IP].dst]["bytes_out"] += size
        traffic[pkt[IP].src]["packets"] += 1

def print_stats():
    print(f"\n{'Host':20s} {'In (KB)':>10s} {'Out (KB)':>10s} {'Packets':>10s}")
    print("-" * 55)
    for host, stats in sorted(traffic.items(), key=lambda x: x[1]["bytes_in"], reverse=True)[:10]:
        print(f"{host:20s} {stats['bytes_in']/1024:10.1f} {stats['bytes_out']/1024:10.1f} {stats['packets']:10d}")

# Sniff for 30 seconds, then print stats
sniff(prn=monitor, timeout=30, iface="eth0")
print_stats()

Full example: code/bandwidth_monitor.py


Monitoring with psutil and socket

For monitoring without raw packets (no root required):

import psutil
import time

def monitor_connections():
    """List all active network connections."""
    for conn in psutil.net_connections(kind="inet"):
        laddr = f"{conn.laddr.ip}:{conn.laddr.port}" if conn.laddr else ""
        raddr = f"{conn.raddr.ip}:{conn.raddr.port}" if conn.raddr else ""
        print(f"{conn.status:12s} {laddr:25s}{raddr:25s} PID={conn.pid}")

def monitor_bandwidth(interval: float = 1.0):
    """Monitor network I/O rates."""
    prev = psutil.net_io_counters()
    while True:
        time.sleep(interval)
        curr = psutil.net_io_counters()
        sent = (curr.bytes_sent - prev.bytes_sent) / 1024
        recv = (curr.bytes_recv - prev.bytes_recv) / 1024
        print(f"{sent:.1f} KB/s  ↓ {recv:.1f} KB/s")
        prev = curr

Key Takeaways


← Previous: HTTP, APIs, and WebSockets Table of Contents Next: Network Automation and Configuration →