pcap:   reading and dissecting classic pcap capture files
1 Scope
2 Reading captures
pcap-header
packet
pcap-read
pcap-read-file
pcap-read-bytes
pcap-read-header
3 Dissection
ethernet-frame
ipv4-header
ipv6-header
tcp-header
udp-header
decode-ethernet
decode-ipv4
decode-ipv6
decode-tcp
decode-udp
decode-packet
4 Errors and limitations
9.3

pcap: reading and dissecting classic pcap capture files🔗ℹ

 (require pcap) package: pcap

The pcap package reads capture files in the classic pcap format—the file format written by libpcap and tcpdump, as specified by the draft-ietf-opsawg-pcap Internet draft and described on the Wireshark wiki. It is implemented in pure Racket: no FFI and no system dependencies. On top of raw packet data it offers dissection of Ethernet II (including 802.1Q and 802.1ad VLAN tags), IPv4, IPv6 (fixed header only), TCP, and UDP.

1 Scope🔗ℹ

  • All four classic magic-number variants are supported, covering both byte orders and both timestamp resolutions:
    • a1 b2 c3 d4 — big-endian, microseconds

    • d4 c3 b2 a1 — little-endian, microseconds

    • a1 b2 3c 4d — big-endian, nanoseconds

    • 4d 3c b2 a1 — little-endian, nanoseconds

  • pcapng files are not supported. Feeding a pcapng file to any reader in this package raises exn:fail with a “bad magic number” message.

  • Checksums are not validated. IPv6 extension headers are not parsed: decode-ipv6 exposes only the fixed 40-octet header.

2 Reading captures🔗ℹ

struct

(struct pcap-header (link-type snaplen)
    #:extra-constructor-name make-pcap-header)
  link-type : exact-nonnegative-integer?
  snaplen : exact-nonnegative-integer?
File-level information. link-type is the capture’s link-layer header type (the pcap LINKTYPE_ number; Ethernet is 1), normalized to the low 16 bits of the field as the format’s reserved upper bits allow. snaplen is the snapshot length declared by the writer.

struct

(struct packet (timestamp link-type data orig-len)
    #:extra-constructor-name make-packet)
  timestamp : rational?
  link-type : exact-nonnegative-integer?
  data : bytes?
  orig-len : exact-nonnegative-integer?
One captured packet. timestamp is an exact rational number of seconds since the UNIX epoch, combining the record’s seconds and fractional fields; the fractional field is interpreted as microseconds or nanoseconds according to the file’s magic number. link-type repeats the capture’s link type for convenience. data holds exactly the captured octets, i.e. incl_len bytes. orig-len is the packet’s original wire length (orig_len), which is larger than the data length when the capture truncated packets to the snapshot length.

procedure

(pcap-read in) → (listof packet?)

  in : input-port?
Reads every packet record from in and returns them in capture order. Raises exn:fail if the global header is missing or truncated, the magic number is unrecognized, or a packet record or its data is truncated.

procedure

(pcap-read-file path) → (listof packet?)

  path : path-string?
Opens the file at path and reads it with pcap-read.

procedure

(pcap-read-bytes bs) → (listof packet?)

  bs : bytes?
Reads a capture held in memory as bs.

procedure

(pcap-read-header in) → pcap-header?

  in : input-port?
Reads and validates just the 24-octet global header (consuming it from in), without reading packet records. Useful for inspecting a capture’s link type before committing to a full read.

3 Dissection🔗ℹ

struct

(struct ethernet-frame (dst src ether-type payload tagged?)
    #:extra-constructor-name make-ethernet-frame)
  dst : string?
  src : string?
  ether-type : exact-nonnegative-integer?
  payload : 
(or/c bytes? ipv4-header? ipv6-header?
      tcp-header? udp-header?)
  tagged? : boolean?
A decoded Ethernet II frame. dst and src are MAC addresses formatted "aa:bb:cc:dd:ee:ff". 802.1Q, 802.1ad, and the legacy 0x9100 VLAN tags (including QinQ stacks) are skipped: ether-type reports the final inner EtherType and tagged? reports whether any VLAN tag was present.

struct

(struct ipv4-header (src dst protocol ttl options payload)
    #:extra-constructor-name make-ipv4-header)
  src : string?
  dst : string?
  protocol : exact-nonnegative-integer?
  ttl : exact-nonnegative-integer?
  options : bytes?
  payload : (or/c bytes? tcp-header? udp-header?)
A decoded IPv4 packet. src and dst are dotted-quad strings. options holds the bytes between the minimum 20-octet header and the length given by the IHL field (empty for IHL 5). The payload is trimmed to the IP total-length field, which discards Ethernet padding on small frames; a capture truncated below the declared total length yields the remaining bytes.

struct

(struct ipv6-header (src dst next-header hop-limit payload)
    #:extra-constructor-name make-ipv6-header)
  src : string?
  dst : string?
  next-header : exact-nonnegative-integer?
  hop-limit : exact-nonnegative-integer?
  payload : (or/c bytes? tcp-header? udp-header?)
A decoded IPv6 fixed header. Addresses are formatted per RFC 5952 (lowercase hex with the longest zero run compressed to "::", e.g. "2001:db8::1"). Extension headers are not parsed: next-header is the raw protocol number and payload is everything past the fixed 40-octet header.

struct

(struct tcp-header (src-port dst-port seq ack flags payload)
    #:extra-constructor-name make-tcp-header)
  src-port : exact-nonnegative-integer?
  dst-port : exact-nonnegative-integer?
  seq : exact-nonnegative-integer?
  ack : exact-nonnegative-integer?
  flags : exact-nonnegative-integer?
  payload : (or/c bytes? tcp-header? udp-header?)
A decoded TCP segment. flags packs the nine meaningful flag bits (NS, CWR, ECE, URG, ACK, PSH, RST, SYN, FIN), e.g. 2 is SYN and 24 is PSH+ACK.

struct

(struct udp-header (src-port dst-port payload)
    #:extra-constructor-name make-udp-header)
  src-port : exact-nonnegative-integer?
  dst-port : exact-nonnegative-integer?
  payload : (or/c bytes? tcp-header? udp-header?)
A decoded UDP datagram, with the payload trimmed to the UDP length field.

procedure

(decode-ethernet bs) → ethernet-frame?

  bs : bytes?
Decodes one Ethernet II frame, skipping any VLAN tags.

procedure

(decode-ipv4 bs) → ipv4-header?

  bs : bytes?
Decodes one IPv4 header and its payload.

procedure

(decode-ipv6 bs) → ipv6-header?

  bs : bytes?
Decodes one IPv6 fixed header and its payload.

procedure

(decode-tcp bs) → tcp-header?

  bs : bytes?
Decodes one TCP header and its payload.

procedure

(decode-udp bs) → udp-header?

  bs : bytes?
Decodes one UDP header and its payload. Each raises exn:fail when the input is shorter than the corresponding minimum header or carries a malformed length or version field. For example:

Examples:
> (require pcap)
> (define eth
    (decode-ethernet (bytes 170 187 204 221 238 255
                            17 34 51 68 85 102
                            8 0 222 173)))
> (ethernet-frame-dst eth)

"aa:bb:cc:dd:ee:ff"

> (ethernet-frame-ether-type eth)

2048

> (ethernet-frame-tagged? eth)

#f

> (ethernet-frame-payload eth)

#"\336\255"

procedure

(decode-packet pkt) → ethernet-frame?

  pkt : packet?
Convenience: dissects a packet from an Ethernet capture into a nested structure. The result is an ethernet-frame whose payload is an ipv4-header or ipv6-header when the EtherType says so, whose payload in turn is a tcp-header or udp-header when the protocol/next-header field says so, and raw bytes otherwise. If a transport header is truncated or malformed it is left undecoded rather than raising. Use the layer predicates (ipv4-header?, tcp-header?, ...) to navigate the nesting.

4 Errors and limitations🔗ℹ

All failures raise exn:fail with descriptive messages: truncated global header, bad magic number (the message notes that pcapng is unsupported), truncated packet record header, and truncated packet data. A record whose captured length exceeds a 16 MiB safety limit is rejected as probably corrupt so that garbage length fields cannot trigger huge allocations.

Limitations: checksums are never validated; IPv6 extension headers, IP fragmentation, and TCP stream reassembly are out of scope; only Ethernet-captured packets are accepted by decode-packet.