Why does the tcpdump filter `host 192.0.2.10 and port 53 or port 853` show port-853 traffic from other hosts, and how do you fix it?
answer
- and and or rank equally
- grouped left to right
- parentheses inside single quotes
- bare id reuses the last keyword
basics
~20 sIn pcap-filter, and and or have equal precedence and group left to right, so the filter means (host and port 53) or port 853. Write host 192.0.2.10 and (port 53 or 853), single-quoted so the shell keeps the parentheses.
solid answer
~40 spcap-filter gives `and` and `or` **equal precedence** and associates them **left to right**, while `not` binds tightest. So `host 192.0.2.10 and port 53 or port 853` is read as `(host 192.0.2.10 and port 53) or port 853`, and the last primitive matches port 853 from anyone. The fix is explicit grouping: `'host 192.0.2.10 and (port 53 or 853)'`. Inside the parentheses, `853` has no keyword, so the most recent one, `port`, is reused. Quote the whole expression in single quotes, because parentheses are shell metacharacters. The same keyword-reuse rule bites with `not`: `not host 192.0.2.10 and 192.0.2.20` means `not host 192.0.2.10 and host 192.0.2.20`, not `not (host ... and host ...)`.
code
bash · 3 linestcpdump -n -i eth0 'host 192.0.2.10 and (port 53 or 853)'
tcpdump -n -i eth0 'tcp port 5432 and (host 192.0.2.10 or 192.0.2.11) and not port 22'
tcpdump -n -i eth0 -F dns-incident.filtergo deeper
Remember to put the whole filter in single quotes and to use parentheses whenever and and or appear together.
Explain equal precedence with left-to-right grouping, not binding tightest, and how a bare id inherits the last keyword.
Treat filter grouping as evidence integrity: a mis-grouped filter silently captures the wrong traffic, so check it with tcpdump -d before a long capture.
Push for reviewed filter files for recurring incidents so precedence mistakes are caught once, not rediscovered at every outage.
## The operators and their precedence tcpdump's filter language (libpcap's **pcap-filter**) combines primitives with three operators, each with a symbolic synonym: | Word | Symbol | Meaning | |---|---|---| | `not` | `!` | negation | | `and` | `&&` | concatenation: both must hold | | `or` | `\|\|` | alternation: either may hold | The precedence rules are short and differ from most programming languages: - **Negation has the highest precedence.** `not host 192.0.2.10 and port 22` is `(not host 192.0.2.10) and port 22`. - **`and` and `or` have equal precedence and associate left to right.** There is no rule that `and` binds tighter. - The symbolic forms behave exactly like the words: `&&` and `||` rank equally too, so switching notation does not change the grouping. So `host 192.0.2.10 and port 53 or port 853` is evaluated as: ``` (host 192.0.2.10 and port 53) or port 853 ``` The trailing `or port 853` stands on its own and matches every packet to or from port 853 on the wire. In this example the left-to-right reading happens to match what C precedence would give, which is why people stop thinking about it; the habit fails as soon as `or` comes first. `port 853 or port 53 and host 192.0.2.10` is `(port 853 or port 53) and host 192.0.2.10` in pcap-filter, but would group the other way in C. ## The fix: explicit parentheses The reliable habit is to parenthesise every mixed `and` / `or` expression, even where the default grouping would be right: ``` host 192.0.2.10 and (port 53 or 853) ``` Two rules make that compact form correct: 1. **Omitted keywords are inherited.** If an id appears without a keyword, the most recent keyword is assumed, so `port 53 or 853` is `port 53 or port 853`. The whole qualifier list carries over: `tcp dst port ftp or ftp-data or domain` means three `tcp dst port` tests. 2. **Parentheses group primitives**, overriding left-to-right association. The inheritance rule is also a trap with `not`. `not host vs and ace` is short for `not host vs and host ace`; it is **not** `not (host vs and host ace)`. The negation still applies only to the first primitive. If you mean to exclude two hosts, write `not (host 192.0.2.10 or host 192.0.2.20)` or `not host 192.0.2.10 and not host 192.0.2.20`. ## Shell quoting The expression is parsed by libpcap, but it reaches tcpdump through a shell first: - Parentheses, `&`, `|` and `!` are shell metacharacters. Unquoted, `(` starts a subshell or produces a syntax error, `&&` and `||` chain commands, and `!` can trigger history expansion in an interactive bash. - Backslashes used to escape keyword ids, as in `ip proto \tcp`, are eaten by the shell unless quoted. - The safe default is to put the **entire expression in single quotes** as one argument. tcpdump also accepts the expression as several arguments and joins them with spaces before parsing, which is why unquoted simple filters work and grouped ones break. For long or reused expressions, `-F file` reads the filter from a file; any expression also given on the command line is then ignored. That keeps a reviewed incident filter out of shell quoting altogether. ## Checking what you wrote Before starting a long capture with a complex filter, two cheap checks catch most mistakes: 1. Read the expression aloud with explicit parentheses, left to right, and make sure that is what you meant. 2. Compile it with `tcpdump -d` and look at the result. If a primitive you expected to be ANDed with the host test is reachable without it, the grouping is wrong. ## Common incident patterns - Exclude your own SSH session: `'host 192.0.2.10 and not port 22'`. - Two ports for one host: `'host 192.0.2.10 and (port 80 or 443)'`. - Two hosts, one port: `'tcp port 5432 and (host 192.0.2.10 or 192.0.2.11)'`. - Everything except two noisy hosts: `'not (host 192.0.2.50 or 192.0.2.51)'`. Each has a single, visible grouping, and none depends on precedence you have to remember under pressure. The cost of getting it wrong is asymmetric: a filter that is too broad fills the disk or buries the conversation you wanted, and a filter that is too narrow silently drops the evidence, which no later analysis can recover. Ten seconds spent adding parentheses is cheaper than either.
- What does `not host 192.0.2.10 and 192.0.2.20` mean in a tcpdump filter?It means `not host 192.0.2.10 and host 192.0.2.20`: traffic to or from .20 that does not involve .10. The bare id inherits the `host` keyword, and `not` binds only to the first primitive. To exclude both hosts, write `not (host 192.0.2.10 or 192.0.2.20)`.
- When would you use tcpdump's `-F` option instead of typing the filter?When the expression is long, reused across hosts or reviewed by someone else. `-F file` reads the filter from a file, which avoids shell quoting entirely and keeps the exact expression under version control. Any expression typed on the command line as well is ignored, so do not combine the two expecting them to be ANDed.
saying these in an interview costs you the question
- Assumes and binds tighter than or, as in C
- Thinks not applies to every primitive that follows it
- Leaves parentheses unquoted on the shell command line
- Believes tcpdump rejects an expression split across arguments
- Expects -F and a command-line expression to be combined