This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Filters and analysis internals
Display filters
Headers: includes/filters.h. Code: src/filters/.
| File | Job |
|---|---|
filters.c |
The active list and its toggles: filter_apply(), filters_apply_args(), filters_remove_at(), filters_clear(). |
filter_print.c |
The Active filters (OR): ... lines. |
filter_match.c |
filters_match(), one helper per filter type. |
filter_parse.c |
Names and values: strict dotted IPv4, IPv6 with at most one ::, port 1–65535, protocol and EtherType names. |
local.c |
The machine's own addresses, for me. |
The name tables (filter_type_names[], filter_shortcuts[]) are in src/static_data/static_filter.c. They also produce the Available filters: line and the unknown-filter error, so a new filter shows up everywhere once it's in the table.
Representation
netotter_t.filters is a list of filter_t: a FILTER_TYPE and a value union, zeroed before it's filled so two filters compare with memcmp. The union holds an IPv4 or IPv6 address, a port, an interface name (FK_IFACE, up to 15 characters, or all), a protocol number (FK_PROTO) or an EtherType (FK_ETYPE).
Toggles
filter_apply():
- A single filter is added when absent and removed when present.
- A shortcut (
ipv4,ipv6,port,me) adds whichever of its two filters is missing. It removes both only when both are active.
Matching
filters_match(filters, mode, local, packet):
- No filter: everything is shown.
FM_OR: a packet is shown when any filter matches.FM_AND: a packet is shown when every filter matches. This is naive on purpose: a shortcut in AND mode needs source and destination to match.- ARP:
packet_ipv4()makes ARP packets answer the IPv4 andmefilters, with the sender IP as source and the target IP as destination. proto: compares the IPv4 protocol, or the IPv6 next header or the protocol after the extension headers (ipv6->upper).etype: comparesnet_proto.llcis stored as 0 and matches every value up to 1500.
Filters only affect display: display_packets() (the l list and the CLI list) and -o / o saves. Capture and i cover every packet.
Own addresses: me
netotter_t.local is a list of local_addr_t (interface name, family, address, saved, renamed):
- Filling: the backend's
local_addresses_refresh()fills it (getifaddrs()on Linux,GetAdaptersAddresseson Windows). - Refreshing:
local_refresh()runs at startup and then every 30 s (LOCAL_REFRESH_S) after the ticks. - Only growing: addresses are only ever added. A temporary IPv6 address that expired still belongs to this machine in older packets.
- From a file: on
-I, saved addresses are merged withlocal_add(..., saved = 1). An address that is also live stays live, andMmarks the others(from the file).
The analysis view's output
includes/analysis.h, src/analysis/view.c, src/analysis/renames.c, libs/utils/my_write.c.
Display code never calls write(). It calls my_write(fd, ...):
- with a normal
fd, that'swrite(); - with
OUT_MEMORY, it appends to a growable buffer.
my_putstr / PSTR, my_putunbr / PNBR, everything in src/display/ and filter_print.c go through it.
flowchart LR
vb["view_begin()"] -->|classic| fd1["fd 1: written as it comes"]
vb -->|analysis| mem["OUT_MEMORY buffer"]
mem --> ve["view_end()"]
ve --> rw["renames_write()<br/>longest match at each position"]
rw --> out["stdout"]
l and i are wrapped in view_begin() / view_end():
- Classic view (or no renaming): output goes to fd 1.
- Analysis view:
view_begin()turns on port names (display_port_names(1)) and hostnames (display_hosts()), and sends output to memory.view_end()takes the text (my_out_take()) and prints it throughrenames_write(), then turns them off again.
A memory buffer was chosen over the alternatives because the code must build on Linux and Windows. A pipe blocks at 64 KiB, and Windows has no named pipes. tmpfile() fails at the drive root on some Windows runtimes.
Renamings
netotter_t.renames is a list of rename_t (from, to, kind). renames_write() scans the text and, at each position, replaces the longest matching from.
| Kind | Made by | Matches | Saved |
|---|---|---|---|
RN_TEXT |
n <text>,<name> (rename_set()) |
any plain substring | yes |
RN_IPV4 / RN_IPV6 |
local_sync_renames(): <address> -> me |
whole addresses only (address_char() checks the neighbouring characters) |
no |
Automatic renamings:
- Whole addresses:
192.0.2.10doesn't hit192.0.2.103, and::1doesn't hitfe80::1. An IPv4 address may be followed by:(its port). - User text wins:
rename_set_auto()never replaces a user renaming of the same text. Typing a renaming for an automatic one turns it intoRN_TEXT. - When they're made:
local_sync_renames()runs whenlocal_refresh()adds addresses and afterload_local().local_addr_t.renamedmarks the done ones, so ann <address>removal lasts for the session.
Port names
port_names[] (src/static_data/static_display.c) is a range table: {low, high, name, app}.
- Order: the first match wins, so single ports go before the ranges that contain them.
- Width:
nameis exactly 5 characters, the width of the longest port number, so endpoint columns stay aligned. - Apps:
appis the precise application (Minecraft (Java)), printed oni'sApplicationline, or NULL for plain protocols. - Ephemeral ports: game ranges stay below 32768, so random client ports aren't mislabelled.
Port names are printed before the renamings apply, so a renaming can rename them.
User guide
- Overview
- Installation
- Getting started
- Command-line options
- Interactive commands
- Display filters
- Analysis view
- Reverse DNS and privacy
- Anomaly detection
- Save files
- Memory and limits
- Troubleshooting
Developer documentation
- Overview
- Building
- Architecture
- OS backends
- Packet parsing
- Packet storage
- Save format
- Hostname learning
- Reverse DNS internals
- Filters and analysis internals
- Anomaly lab
- Coding conventions
- Function explorer
Learning