Lesson 08 / 16
Packet Analysis Tools
The capture filter is a single lever, and it costs at both ends: in a window of ninety-eight flows, the narrowest filter never sees 91 flows and misses 21 of 22 heavy events, while unfiltered capture drops 776,196 bytes because they do not fit the buffer.
Contents
The previous two lessons both relied on the device’s own account. The device produced the flow record, the device answered the query, the device sent the notification. If a device has a behavior it never reports, there is no way to learn it by asking the device.
This lesson changes the source and looks at the bytes crossing the wire itself. An analyzer placed at a point copies and records the packets passing through it. Copying is not unlimited: a capture filter chooses which packets get copied, and the copies are written into a limited buffer. The filter is a lever — a single rule applies to all forty subjects’ traffic at once. What this lesson measures is not what the filter sees, but what it fails to see.
The use of analyzer tools and the capture procedure is the subject of the Security Tooling and Penetration Testing course and is not repeated here; nor are tool names written. What is measured here is not the tool, but the rule itself.
Capture’s Two Limits
Capture loses at two separate points, and the two work in opposite directions.
The first limit is the filter. A packet that does not pass the filter is never copied at all. This is not a loss but a choice — until the behavior being sought falls outside the filter. A narrow filter protects the buffer by filtering out irrelevant traffic; in exchange, it can never bring back what it filtered out, because a packet that was never copied does not exist.
The second limit is the buffer. Packets that pass the filter are written to a buffer, and once the buffer fills, the next incoming packet is dropped. This drop is silent: there is no line in the record that says “a packet was dropped here,” only packets that are not there. A wide filter fills the buffer quickly, and the drop happens right in the middle of the traffic the filter cares about.
Both limits are tied to the same lever. When the filter is narrowed, the first limit grows and the second shrinks; when it is widened, the reverse happens. There is no loss-free setting in between, only a choice of where the loss falls.
Filter Syntax
Capture filters are written over the packet’s header fields. The dump below shows the taught syntax; it has not been executed and carries no numeric claim.
# taught filter syntax , not executed single field tcp looks at the protocol field port 443 source or destination port src port 443 source port only host station.example source or destination host compound tcp and port 443 tcp and (port 80 or port 443 or port 8080) not port 22 in-packet position tcp[13] & 2 != 0 segments with the SYN flag set ip[6:2] & 0x1fff = 0 unfragmented first fragment
Two things deserve attention. First, the filter looks at header fields; it cannot
look at the payload’s content, because the decision has to be made before the packet is
copied. Second, a negation like not port 22 widens the filter — a rule that looks
narrow is actually a wide one if what it excludes is small. The measure of a filter’s
width is not the length of its syntax but the number of matching flows.
Snap Length and Flow Reassembly
The snap length determines how much of each packet is kept. If only headers are to be kept, a few dozen bytes per packet is enough; if the full payload is wanted, the entire packet is written, and the buffer fills ten times faster in the same span.
Snap length directly limits flow reassembly. Reassembly is merging the packets belonging to the same flow by their sequence numbers to recover the original byte stream. Headers are enough if the question is “when did this flow start, how many bytes did it carry, how did it close.” If the question is “what did this flow carry,” headers are not enough, and a truncated record can never answer that question.
Reassembly’s second condition is continuity: if one of the flow’s packets was dropped, the flow cannot be fully reconstructed. This is why, in the measurement, a flow can land in one of three separate outcomes — fully captured, left half-done, or never fit into the buffer at all.
The measurement’s assumptions:
- OB14 — The forty subjects are the course’s fixed population; each subject produces between one and four flows in the window, and the flows are ordered by start time.
- OB15 — The capture buffer is 1,200,000 bytes and is not flushed during the window. In a real capture, the buffer is flushed to disk; keeping it fixed here isolates the drop’s dependence on filter width.
- OB16 — Snap length is fixed at 96 bytes/packet and is not a lever in this lesson. Only the filter is changed.
- OB17 — The buffer fills in time order. A flow that ends by the moment it fills is fully captured, a flow that straddles that moment is left half-done, and a flow that starts afterward is never written at all.
- OB18 — A dropped packet leaves no trace; the measurement reads the dropped bytes from the oracle, not from the record. In a real capture, this number can only be estimated from a counter.
- OB19 — A heavy event is the oracle’s knowledge. If even a single flow of a subject was captured, that subject’s heavy events count as visible; if none of its flows were captured, all of them are missed.
- OB20 — The filters are nested: every filter matches all the flows the previous one matched. Width is thus measured on a single one-directional axis.
- OB21 — The observation point sees all forty subjects’ flows. This is a convenience with no counterpart in the field; the measurement therefore only counts the loss from the filter and the buffer, not the loss from position.
Measurement
"""Packet capture: the capture filter is a single lever, what is measured is what it fails to see. Part 1 - forty subjects' flows, ordered by start time. Part 2 - five filter widths: how many flows a narrow filter misses, how many bytes a wide one drops. """ SEED = 20260812 BUFFER = 1_200_000 # bytes, capture buffer SNAP = 96 # bytes, most kept per packet def rng(seed): state = seed % 2147483646 + 1 def draw(n): nonlocal state state = (state * 48271) % 2147483647 return state % n return draw def subjects(count=40, seed=SEED): draw, items = rng(seed), [] for i in range(count): items.append({"id": i + 1, "capacity": 20 + draw(81), "latency": 5 + draw(45), "class": ("interactive", "batch", "standby")[draw(3)], "special": draw(9) == 0}) return items def events(subs, count=400, seed=SEED + 3): draw, items = rng(seed), [] for i in range(count): s = subs[draw(len(subs))] items.append({"id": i + 1, "subject": s["id"], "heavy": draw(25) == 0}) return items def flows(subs, seed=SEED + 7): """Each subject produces between one and four flows; the list is ordered by start time.""" draw, items = rng(seed), [] for s in subs: for _ in range(1 + draw(4)): items.append({"subject": s["id"], "protocol": ("TCP", "UDP", "ICMP")[draw(3)], "port": (22, 53, 80, 123, 443, 8080)[draw(6)], "packets": 20 + draw(400), "size": 64 + draw(1400), "start": draw(1000)}) return sorted(items, key=lambda a: (a["start"], a["subject"])) def capture(fl, filt, buffer=BUFFER, snap=SNAP): """Flows passing the filter are written to the buffer; once it fills, the remaining bytes drop.""" filled = matched = whole = half = overflow = dropped = 0 seen = set() for a in fl: if not filt(a): continue matched += 1 size = a["packets"] * min(a["size"], snap) if filled + size <= buffer: filled, whole = filled + size, whole + 1 seen.add(a["subject"]) elif filled < buffer: half += 1 dropped += filled + size - buffer filled = buffer seen.add(a["subject"]) else: overflow += 1 dropped += size return matched, whole, half, overflow, dropped, seen FILTERS = { "TCP port 443": lambda a: a["protocol"] == "TCP" and a["port"] == 443, "TCP three ports": lambda a: a["protocol"] == "TCP" and a["port"] in (80, 443, 8080), "TCP only": lambda a: a["protocol"] == "TCP", "TCP and UDP": lambda a: a["protocol"] in ("TCP", "UDP"), "unfiltered": lambda a: True, } subs = subjects() evts = events(subs) fl = flows(subs) print(f"subject {len(subs)} | flow {len(fl)} | packet {sum(a['packets'] for a in fl)}" f" | heavy event {sum(e['heavy'] for e in evts)}") print(f"buffer {BUFFER} bytes | snap {SNAP} bytes/packet" f" | snapped total {sum(a['packets'] * min(a['size'], SNAP) for a in fl)} bytes" f" | unsnapped {sum(a['packets'] * a['size'] for a in fl)} bytes") print() print(f"{'filter':<15s} {'matched':>8s} {'outside':>8s} {'whole':>6s} {'half':>5s} " f"{'overflow':>9s} {'bytes dropped':>13s} {'invisible':>10s} {'heavy missed':>13s}") for name, f in FILTERS.items(): e, whole, half, over, dropped, seen = capture(fl, f) missed = sum(1 for x in evts if x["heavy"] and x["subject"] not in seen) print(f"{name:<15s} {e:8d} {len(fl) - e:8d} {whole:6d} {half:5d} {over:9d} " f"{dropped:13d} {len(subs) - len(seen):10d} {missed:13d}")
subject 40 | flow 98 | packet 20631 | heavy event 22 buffer 1200000 bytes | snap 96 bytes/packet | snapped total 1976196 bytes | unsnapped 15032397 bytes filter matched outside whole half overflow bytes dropped invisible heavy missed TCP port 443 7 91 7 0 0 0 33 21 TCP three ports 17 81 17 0 0 0 23 13 TCP only 38 60 38 0 0 0 15 6 TCP and UDP 65 33 58 1 6 188868 11 4 unfiltered 98 0 65 1 32 776196 5 2
What the Narrow Filter Misses
The first row is the narrowest rule, and it works exactly as intended: all seven of the matching 7 flows are captured intact, not a single byte drops from the buffer, the record is small, and it is easy to read. Nothing is broken on this row.
What is broken sits to the row’s right. 91 of the ninety-eight flows are left outside the filter, and 33 of the forty subjects never appear in the record at all. 21 of the twenty-two heavy events are missed. The record is clean, because only what was meant to be looked at entered it; that is exactly the problem — the record was built on the assumption that what would be asked of it was already known.
As the filter widens, this side improves. With TCP three ports, invisible subjects
fall to 23; with TCP only, to 15; missed heavy events become 13 and 6.
Through the third row, the buffer is never strained at all: bytes dropped is 0. In
this range, the lever looks free.
What the Wide Filter Misses
In the fourth row, the second limit kicks in. The TCP and UDP filter matches 65
flows, but only 58 of them are captured whole: 1 flow is left half-done and
6 flows never fit in the buffer at all. Bytes dropped is 188,868.
In unfiltered capture, the table flips. The filter now leaves nothing outside — the
outside column is 0. In exchange, only 65 of the ninety-eight flows are
captured whole, 32 never fit in the buffer at all, and 776,196 bytes drop. The
snapped total is 1,976,196 bytes; that is, roughly two-fifths of the data meant to
be captured never enters the record at all.
The most striking column is invisible subjects: in unfiltered capture, this number is 5, and it is not zero. Even though the filter excluded no subject, five subjects are absent from the record, because their flows started after the moment the buffer filled. Widening the filter did not increase visibility — it moved the loss from the filter to the buffer.
The qualitative difference between the two losses matters. A flow the filter excludes was excluded knowingly; whoever reads the rule can say what was left out. A flow dropped from the buffer, by contrast, was dropped without anyone knowing, and the record itself carries no field showing this. The second loss is more dangerous even when it carries away fewer bytes than the first, because whoever reads the record does not notice the gap.
The Uncounted Third Loss
The measurement assumes the observation point sees all forty subjects’ flows. This assumption does not hold in the field, and where it fails to hold, a third loss is born.
The analyzer is placed at one point and only sees packets that pass through that point. If two subjects talk to each other on the near side of that point, the traffic between them is never copied at all — no matter how wide the filter or how large the buffer. In the same way, an analyzer attached to one port of a switch does not see traffic exchanged among the other ports; that requires the traffic to be separately mirrored, and mirroring carries its own limit: if the mirrored port’s speed is lower than the combined speed of the sources, packets drop during mirroring too.
This loss differs from the other two in that it cannot be estimated by reading the filter rule. What the narrow filter excludes is written into the rule itself; the amount dropped from the buffer can be read off a counter. What the position fails to see, though, can only be inferred from the topology, and it leaves no trace in the capture record at all. The three losses share one consequence: an empty record does not mean nothing happened there.
Narrowing the Losses
The lever’s tail cannot be removed; three narrowings make it measurable.
First narrowing — count the drops. The capture interface keeps a counter for
dropped packets. The counter is not sampled and is not affected by the filter; if the
record is incomplete, it is the only field that says so. Reading the counter every time
the record is opened gives the field counterpart of the overflow column.
Second narrowing — choose the snap length by the question. If the full packets in the measured window had been kept, the total would be 15,032,397 bytes; with a 96-byte snap, it falls to 1,976,196 bytes, roughly one-seventh. The same buffer, with the same filter, then covers a window seven times as long. If the question is a flow’s existence and timing, this trade is close to free. If the question is content, the snap must be removed, but then the window must be shortened; the product of the two settings is constant.
Third narrowing — write the filter by the oracle, not the question. In the measurement above, the narrowest filter missed twenty-one of twenty-two heavy events. Because which ports the heavy events pass through is unknown, a narrow filter is gambling. The workable rule is this: the capture filter is written to cover the subjects invisible in telemetry. The previous two lessons were counting who is invisible; this lesson says where that list gets written.
Summary
- Capture loses in two places: a packet that does not pass the filter is never copied, and a packet that does not fit the buffer drops silently; the two losses are opposite directions of the same lever.
- The narrowest filter captures 7 flows and drops no bytes at all, but it leaves 91 flows outside, never shows 33 subjects, and misses 21 of 22 heavy events.
- Unfiltered capture leaves no flow outside, but 32 flows never fit the buffer and 776,196 bytes drop; roughly two-fifths of the snapped total never enters the record.
- Even in unfiltered capture, 5 subjects stay invisible: flows that start after the buffer fills never enter the record, even though the filter excluded nothing.
- A flow the filter excludes is excluded knowingly; a flow dropped from the buffer drops unknowingly; the second loss is more dangerous even when it is smaller, because it leaves no trace in the record.
- A third loss sits outside the measurement and comes from the observation point: traffic that does not pass through that point is invisible at any filter width, and this gap can be read from neither the rule nor a counter — only inferred from the topology.
Next Step
The three measurements so far all looked from inside the network: the device’s record, the device’s reply, the packet crossing the wire. All three had individual devices as their subject. The real question asked in operations, though, is asked end to end — can one end reach the other, and how long does it take? The next lesson builds this single indicator and asks it backwards: which of the forty subjects does an end-to-end health value fail to represent, and how many subjects stay in the tail while the indicator reads green?
To keep your progress and take notes, Log in
My notes
Log in to take notes.