As part of my PhD-study, I was asked to count the number of lost ACK frames on a wifi link. I accomplished this by running tcpdump on one of my two routers (setup for IBSS) in the following steps…

First I setup netcat on my laptop to retrieve the tcpdump-data:

nc -l 6789 > dump.pcap

Then create a monitor interface on the receiving router and start tcpdump:

# Create a monitor interface
iw phy0 interface add mon0 type monitor flags none
ip link set dev mon0 up

# Make tcpdump capture frame destined to the router and pipe to my laptop:
laptop_ip="172.26.72.81"
router_mac=$(cat /sys/class/net/wlan0/address)
tcpdump -i mon0 -w - -s 128 wlan type data and ether dst $router_mac | nc $laptop_ip 6789

Now generate some data to the router. I used iperf in UDP mode, but make sure the rate is not too high, as tcpdump might drop frames. Once you feel that enough data is captured, stop tcpdump on the router.

On my laptop, I now have dump.pcap. To parse it I used this script:

parse_pcap.py download
#!/usr/bin/env python2

import sys
import dpkt
import struct
import socket

# to collect data run the following commands:
#   iw phy0 interface add mon0 type monitor
#   ip link set dev mon0 up
#   tcpdump -i mon0 -w dump.pcap -s 128 wlan type data and ether dst $(cat /sys/class/net/wlan0/address)

# check argument
if len(sys.argv) < 2:
    print("please specify the dump file to read")
    sys.exit(1)

# open dump file
try:
    reader = dpkt.pcap.Reader(open(sys.argv[1]))
except IOError as e:
    print(e)
    sys.exit(1)
except dpkt.Error as e:
    print(e)
    sys.exit(1)

# check for ieee802.11 dump
if not reader.datalink() == dpkt.pcap.DLT_IEEE802_11_RADIO:
    print("pcap file is not from an 802.11 device")
    sys.exit(1)

# initialize values
subtypes = (
        dpkt.ieee80211.D_QOS_DATA,
        dpkt.ieee80211.D_DATA
        )
sequence_index = 22

# initialize counters
retries = 0
frames = 0
sequence = 0
fragment = 0
duplicates = 0
triples = 0
duplicate = 0
subtype = 0
errors = 0
other_frames = 0

# loop packets
for ts,data in reader:
    # read tap header length
    try:
        tap = dpkt.radiotap.Radiotap(data)
    except dpkt.Error as e:
        errors += 1
        continue

    tap_len = socket.ntohs(tap.length)

    # parse ieee80211 header
    try:
        wlan = dpkt.ieee80211.IEEE80211(data[tap_len:])
    except dpkt.Error as e:
        errors += 1
        continue

    # check frame type
    if wlan.subtype not in subtypes:
        other_frames += 1
        continue

    # read sequence control / fragment
    index = tap_len + sequence_index
    i, = struct.unpack("<H", data[index:index+2])
    seq = i >> 4
    frag = i & 0x000F

    # count retries
    if wlan.retry:
        retries += 1

    # check and count duplicates
    if wlan.retry and seq == sequence:
        if duplicate:
            triples += 1
        duplicates += 1
        duplicate = True
    else:
        duplicate = False

    # check for out-of-sequence by more than one
    if not wlan.retry \
            and seq > sequence + 2\
            and sequence\
            and wlan.subtype == subtype:
        print("missed frames {}:{}".format(sequence + 1, seq - 1))

    # check for out-of-sequence by one
    elif not wlan.retry\
            and seq > sequence + 1\
            and sequence \
            and wlan.subtype == subtype:
        print("missed frame {}".format(sequence + 1))

    # update counters
    frames += 1
    sequence = seq
    fragment = frag
    subtype = wlan.subtype

# print results
print("data frames:  {:6}".format(frames))
print("retries:      {:6}".format(retries))
print("duplicates:   {:6}".format(duplicates))
if triples:
    print("triplicates:  {:6}".format(triples))
if other_frames:
    print("other frames: {:6}".format(other_frames))
if errors:
    print("errors:   {:6}".format(errors))

When calling the script it will produce the following output:

./parse.py dump.pcap
data frames:     908
retries:         116
duplicates:       26

This shows that the router received 908 frames, of which 116 was marked as retries by the sender. And out of these 116 retries, 26 frames was already received by the router, which indicates a lost ACK at the sender.