---
title: 'Packet Capture'
source: 'https://academia.sh/en/courses/linux-network-administration/packet-capture'
course: 'Linux Network Administration and Troubleshooting'
language: en
updated: '2026-08-17T18:10:00+00:00'
license: 'CC BY-SA 4.0'
---

# Packet Capture

Measuring capture's elimination power: an unfiltered capture produces 14400 lines and eliminates 6 candidates without finishing the diagnosis; a one-line connection attempt eliminates 7 and finishes it. Elimination per line comes out to 0.0004 against 7.0000.

The previous lesson showed reachability tests hitting a wall: three echo tests and a
connection attempt together still could not split two candidate pairs. At a stall like this,
the next step that comes to mind is usually the same one — not looking at summaries, but at
**the traffic crossing the wire itself.** Packet capture does this, and for that reason it is
counted the toolbox's most detailed tool.

This lesson measures that expectation. The number of lines capture produces is set against the
number of candidate causes it eliminates, and the ratio between the two is computed. The
result is the course's second claim in its harshest form: **more output does not mean a better
diagnosis.** The lesson's second half writes up a boundary that follows from the same
measurement: capture is a **diagnosis** tool, and no use of it beyond diagnosis is covered in
this course.

## What Capture Sees Versus What It Says

Capture records the frames crossing an interface. What it sees is genuinely a lot: every
frame's time, size, direction, and header fields. What it says is only as much as diagnosis can
use, and the operator reduces these thousands of lines to **a single verdict**: which
directions the traffic is flowing in.

The common definition's eight tests are untouched; capture is added in this lesson as a
**ninth** test (**SG10**). Capture is done on the local interface and only over the flow under
diagnosis; its response is reduced to three values (**SG11**): `no-packets`,
`outbound-no-return`, `two-way`. Every captured frame produces one line (**SG12**). Over a
600-second window, the distribution of frames crossing the interface is a mock value and totals
14400 (**SG13**). A filter lowers the number of captured lines but does not change the response
(**SG14**); the narrowest reading is the flow's first six frames (**SG15**). Capture is up and
running before the fault begins (**SG16**).

The three responses correspond directly to the healthy state. If the interface is down, the
address is missing, or the route is missing, the local stack cannot produce frames and the
capture is empty. If the gateway is wrong or the firewall is dropping, frames go out and get no
answer. In the remaining cases, two-way traffic is visible. The meaning of protocol headers and
handshakes is the Computer Networks curriculum's subject and is not opened here.

```text
# example dump , has not been run
$ tcpdump -i <interface> -n -c 200 host <target> and port <port>
$ tcpdump -i <interface> -n -w /tmp/diag.pcap host <target> and port <port>
$ tcpdump -r /tmp/diag.pcap -n | wc -l
```

```python
HEALTHY = {"interface_up": True, "address_set": True, "gateway_correct": True,
           "route_present": True, "resolver_responds": True, "service_listening": True,
           "firewall_passes": True, "path_ok": True}
FAULT = {
    "interface-down":      {"interface_up": False, "address_set": False},
    "no-address":          {"address_set": False},
    "wrong-gateway":       {"gateway_correct": False},
    "missing-route":       {"route_present": False},
    "resolver-silent":     {"resolver_responds": False},
    "service-not-listening": {"service_listening": False},
    "firewall-dropping":   {"firewall_passes": False},
    "large-packet-lost":   {"path_ok": False},
}
CANDIDATES = tuple(FAULT)


def state(a=None):
    d = dict(HEALTHY)
    if a:
        d.update(FAULT[a])
    return d


def path(d):
    return (d["interface_up"] and d["address_set"]
            and d["gateway_correct"] and d["route_present"])


TEST = {
    "interface": lambda d: ("open-addressed" if d["interface_up"] and d["address_set"]
                            else "open-unaddressed" if d["interface_up"] else "down"),
    "connection": lambda d: ("unreachable" if not path(d)
                             else "timeout" if not d["firewall_passes"]
                             else "refused" if not d["service_listening"]
                             else "established"),
    # SG10: the common definition's eight tests are untouched , capture is the NINTH test
    "capture": lambda d: (
        "no-packets" if not (d["interface_up"] and d["address_set"] and d["route_present"])
        else "outbound-no-return" if not (d["gateway_correct"]
                                          and d["firewall_passes"])
        else "two-way"),
}

TRAFFIC = {                       # SG13: mock frame counts over a 600-second window
    "unrelated-neighbor": 9000, "name-query": 1800, "management": 2400,
    "target-other-port": 1080, "target-flow": 120,
}
FILTER = {                       # SG14: a filter lowers lines , NOT the response
    "no filter": list(TRAFFIC),
    "target address": ["target-other-port", "target-flow"],
    "target address + port": ["target-flow"],
}


def lines(name):
    return sum(TRAFFIC[k] for k in FILTER[name])


def groups(kit, candidates=CANDIDATES):
    o = {}
    for a in candidates:
        o.setdefault(tuple(TEST[s](state(a)) for s in kit), []).append(a)
    return o


def eliminates(kit, real, candidates=CANDIDATES):
    pattern = tuple(TEST[s](state(real)) for s in kit)
    remaining = groups(kit, candidates)[pattern]
    return len(candidates) - len(remaining), len(remaining)


ORACLE = "firewall-dropping"
print("fault                       capture              connection")
for a in CANDIDATES:
    print(f"  {a:26s}{TEST['capture'](state(a)):21s}{TEST['connection'](state(a))}")
print()
print("oracle:", ORACLE)
for s in ("capture", "connection", "interface"):
    e, k = eliminates([s], ORACLE)
    print(f"  {s:9s} response {TEST[s](state(ORACLE)):20s} eliminated {e}  left {k}")
print()
print("line count and eliminated candidates per line (oracle fixed)")
for name in FILTER:
    n = lines(name)
    e, _ = eliminates(["capture"], ORACLE)
    print(f"  capture / {name:26s} lines {n:6d}  eliminated {e}  per line {e / n:.4f}")
FIRST = 6                          # SG15: the flow's first 6 frames
e, _ = eliminates(["capture"], ORACLE)
print(f"  capture / {'first frames of flow':26s} lines {FIRST:6d}  eliminated {e}"
      f"  per line {e / FIRST:.4f}")
eb, _ = eliminates(["connection"], ORACLE)
print(f"  connection attempt{'':20s} lines {1:6d}  eliminated {eb}  per line {eb / 1:.4f}")
print()
print("test kit                  groups  unsplit  singleton  largest group")
for kit in (["capture"], ["connection"], ["interface"],
            ["connection", "capture"], ["connection", "interface"]):
    o = groups(kit)
    print(f"  {'+'.join(kit):25s} {len(o):4d} {len([v for v in o.values() if len(v) > 1]):12d}"
          f" {len([v for v in o.values() if len(v) == 1]):6d} {max(len(v) for v in o.values()):14d}")
print()
print("candidate set sweep: what capture and connection eliminate (set growing)")
kit = [ORACLE]
for a in CANDIDATES:
    if a not in kit:
        kit.append(a)
        ec, kc = eliminates(["capture"], ORACLE, tuple(kit))
        eb, kb = eliminates(["connection"], ORACLE, tuple(kit))
        print(f"  candidate {len(kit)}  capture eliminated {ec} left {kc}"
              f"   connection eliminated {eb} left {kb}")
```

```
fault                       capture              connection
  interface-down            no-packets           unreachable
  no-address                no-packets           unreachable
  wrong-gateway             outbound-no-return   unreachable
  missing-route             no-packets           unreachable
  resolver-silent           two-way              established
  service-not-listening     two-way              refused
  firewall-dropping         outbound-no-return   timeout
  large-packet-lost         two-way              established

oracle: firewall-dropping
  capture   response outbound-no-return   eliminated 6  left 2
  connection response timeout              eliminated 7  left 1
  interface response open-addressed       eliminated 2  left 6

line count and eliminated candidates per line (oracle fixed)
  capture / no filter                  lines  14400  eliminated 6  per line 0.0004
  capture / target address             lines   1200  eliminated 6  per line 0.0050
  capture / target address + port      lines    120  eliminated 6  per line 0.0500
  capture / first frames of flow       lines      6  eliminated 6  per line 1.0000
  connection attempt                     lines      1  eliminated 7  per line 7.0000

test kit                  groups  unsplit  singleton  largest group
  capture                      3            3      0              3
  connection                   4            2      2              4
  interface                    3            1      2              6
  connection+capture           5            2      3              3
  connection+interface         6            2      4              2

candidate set sweep: what capture and connection eliminate (set growing)
  candidate 2  capture eliminated 1 left 1   connection eliminated 1 left 1
  candidate 3  capture eliminated 2 left 1   connection eliminated 2 left 1
  candidate 4  capture eliminated 2 left 2   connection eliminated 3 left 1
  candidate 5  capture eliminated 3 left 2   connection eliminated 4 left 1
  candidate 6  capture eliminated 4 left 2   connection eliminated 5 left 1
  candidate 7  capture eliminated 5 left 2   connection eliminated 6 left 1
  candidate 8  capture eliminated 6 left 2   connection eliminated 7 left 1
```

## Oracle, Test, Eliminated Candidate

Three numbers sit side by side. **Oracle:** the real fault is `firewall-dropping`. **Test:**
unfiltered capture returns `outbound-no-return`. **Eliminated candidate:** this response
eliminates **6** of eight candidates and leaves **2**; the diagnosis does not finish.

Under the same oracle, a one-line connection attempt returns `timeout`, eliminates **7**
candidates, and leaves **1**; the diagnosis finishes. The gap is a single candidate, but that
candidate's name is `wrong-gateway`, and capture carries it all the way to the end. The reason
is simple: what capture sees is the fact "it went out, it did not come back"; it cannot see
**why it did not come back.** The gateway being wrong and the firewall dropping look identical
to an observer watching the interface.

The same blindness sits at the other end of the table too. A `two-way` response fits three
candidates at once: with the resolver silent, with the service not listening, and with a large
packet lost, two-way traffic is visible on the interface in all three cases. An operator
reading the recording says "traffic is flowing," and that is true; the user still cannot reach
the service. What capture can say is **that frames exist**, not that the exchange succeeded.
The same three candidates fall into two separate responses in the four-valued connection
attempt.

The group table sums this up on its own. Capture splits eight candidates into 3 groups and
**no group is left with a single candidate**: capture alone cannot pin down a single fault. The
connection attempt produces 4 groups, and **2** of them hold a single candidate. A one-line
response does what a fourteen-thousand-line recording cannot.

## Elimination Per Line

The line-count column gives the real comparison. Unfiltered capture produces **14400** lines
and eliminates 6 candidates: **0.0004** candidates per line. Filtered by target address, lines
drop to 1200; filtered by target address and port, to 120; the eliminated candidate count
**does not change**, and elimination per line becomes 0.0050 and 0.0500. Reading only the
flow's first six frames brings the ratio up to 1.0000. The connection attempt's per-line ratio
is **7.0000**.

Two readings follow. The first is the claim itself, directly: growing the line count from six
to 14400, that is, **2400 times**, does not raise elimination power **at all**. The information
drawn from capture's 14400 lines is the same as the information drawn from that same capture's
first six lines. Extra lines eliminate no new candidate; they only add to reading time, file
size, and the chance of latching onto the wrong pattern.

Extra lines have a second cost too, and it sits outside the measurement. Capture has to process
every frame it records; once the rate rises, the recording itself starts **dropping** frames.
The tool producing the most output is exactly the tool that can lose evidence at the moment the
load is highest, and a dropped frame does not show up as a gap in the recording, it shows up as
an event that never happened. A narrow filter lowers this risk too: when fewer frames need
recording, the odds of a drop fall. So a filter does not just make reading easier, it also
protects the recording's **completeness**.

The second reading is what a filter does. A filter does not raise elimination power, it lowers
**effort** (**SG14**). This distinction is commonly confused in diagnosis: an operator looking
with a narrow filter reaches a conclusion faster, but the conclusion reached is no more certain
than the one reached with a wide filter. A filter is not a diagnostic decision, it is a reading
convenience. A filter's real risk runs the other way: a badly set filter can leave out the
frames that are the only evidence of the fault, and the recording says "no packets at all."
This is a fourth cause producing the `no-packets` response, and it is not in the candidate set.

## Where Capture Actually Earns Its Keep

The measurement does not show capture is unnecessary; it shows **where it belongs**. Used
together with the connection attempt, group count climbs from 4 to 5, single-candidate group
count from 2 to 3. Capture splits off a spot the connection attempt could not: within the four
candidates under the `unreachable` response, it marks the `wrong-gateway` one by showing that
frames really did go out.

There is, however, a test that does the same job much more cheaply. Adding a one-line
interface reading to the connection attempt brings group count to **6**, single-candidate group
count to **4**, and the largest group drops from 3 to **2**. That is, a two-line configuration
reading eliminates more at that same point than a 14400-line capture. Capture earns its keep
when the remaining ambiguity is about the **existence of frames**; when the ambiguity is about
configuration, reading the configuration is what is needed.

Capture's second real function does not show up in the measurement: **timing**. The time
between frames, retry intervals, and how much the return is delayed exist in no other test.
This information eliminates no candidate, but it tells the duration; in an intermittent fault
it is the only evidence, and that is the next lesson's subject.

## Capture's Boundary

In this course, capture is a **diagnosis** tool, and no use of it beyond diagnosis is covered.
Seizing someone else's traffic, spoofing identity, and evading monitoring are procedures
written in **no lesson**; these are the Cybersecurity curriculum's subject, and even there they
are treated not as a procedure but as a surface to be defended.

The boundary itself follows from the measurement too. The line count needed for diagnosis is
**six**; if the recording collects more than that, the extra adds nothing to the diagnosis. So
the correct setup for capture is narrow: one interface, one target, one port, a counted number
of frames. A wide setup is not just wasteful; it also records the content of unrelated flows,
and that content belongs to no one's diagnosis.

Three operating rules follow directly from this. Recording is bounded by **scope**: the filter
is the flow under diagnosis. Recording is bounded by **duration**: frame count or duration is
set in advance. The recording file is tied to a **retention policy**; the retention question
measured for logs in the system administration course applies to the capture file exactly the
same way, and a file is as much a burden as it is evidence.

## Candidate Set Sweep

The sweep sets two tests side by side as the candidate set grows. In a two-candidate set, both
eliminate 1 candidate and both finish the diagnosis; their difference is invisible. Once a
fourth candidate is added, the paths split: the connection attempt eliminates 3 and stays at 1
left, capture eliminates 2 and climbs to **2** left. This gap holds through the eighth
candidate; capture stays exactly **one** candidate behind at every step.

What follows from this also bounds how much the measurement depends on the mock setup.
Capture's weakness does not come from the line count or the chosen frame distribution; it comes
from the **narrowness of the response space**. A three-valued response splits eight candidates
into at best 3 groups, and even the smallest of those groups can hold more than one. As the
candidate count grows, this narrowness grows with it: a one-candidate gap between the
four-valued connection attempt and the three-valued capture widens further on a longer list.
Every new candidate added to the list lowers the share the narrow-response test eliminates.

## Summary

- Unfiltered capture produces **14400** lines, eliminates **6** candidates, and leaves **2**
  behind without finishing the diagnosis; a one-line connection attempt eliminates **7** and
  finishes it.
- Eliminated candidates per line: **0.0004** for unfiltered capture, **7.0000** for the
  connection attempt. Growing the line count does not raise elimination power.
- Capture alone never brings **any** fault down to a single candidate; the connection attempt
  brings **2** down to one, an interface reading also brings **2** down to one.
- A filter lowers effort, not elimination power; a badly set filter can leave out the only
  evidence of the fault.
- Capture earns its keep on ambiguity about the existence of frames and their timing; on
  ambiguity about configuration, a two-line reading eliminates more.
- Capture is a diagnosis tool: it is bounded by scope, duration, and retention; use beyond
  diagnosis is not covered in this course.

## Next Step

This whole lesson's measurement rested on a single assumption: capture was up and running
before the fault began (**SG16**). Once that assumption is lifted, the numbers collapse,
because an event with no recording looks as if it never happened. The next lesson takes up
common fault patterns by their signatures and pays the third claim: while an intermittent fault
lasts 84 seconds in a 600-second window, a recording starting at the 300th second sees half the
events, one starting at the 500th sees less than a sixth; and a dropped packet's trace is zero
lines while the recording is off.
