Which tcpdump filter shows only the initial SYN of each TCP handshake, not the SYN-ACK, and why is `tcp[tcpflags] & tcp-syn != 0` not enough?
answer
- mask two bits, compare one
- tcpflags is byte 13
- SYN-ACK also has SYN set
- exact equality misses ECN SYNs
basics
~20 stcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn keeps the SYN and ACK bits and requires SYN alone. Testing only the SYN bit also matches every SYN-ACK, because the reply carries SYN too. The numeric form is tcp[13] & 0x12 == 2.
solid answer
~40 sUse `'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'`. `tcpflags` is a named offset for byte 13 of the TCP header, and `tcp-syn` (0x02) and `tcp-ack` (0x10) are named values, so this is the same as `tcp[13] & 0x12 == 2`: mask the SYN and ACK bits, then require SYN set and ACK clear. `tcp[tcpflags] & tcp-syn != 0` only asks whether SYN is set, so it also matches the SYN-ACK. The exact test `tcp[13] == 2` is the opposite mistake: it misses SYNs that carry other bits, such as the ECE and CWR bits an ECN-capable client sets on its SYN. In pcap-filter the bitwise `&` binds tighter than `==`, so no extra parentheses are needed, and the expression must be quoted because `&` and `|` are shell metacharacters.
go deeper
Recall that tcp[tcpflags] reads the flags byte and that tcp-syn and tcp-ack are named values you can mask with.
Explain mask-then-compare: why a single-bit test catches SYN-ACKs, why exact equality misses ECN SYNs, and why & binds tighter than == here.
Use flag filters to measure connection attempts cheaply during an incident, and know which address families and fragments the accessor silently skips.
Prefer flag-level capture over full-payload capture where the question is about connection behaviour; it shrinks both disk use and the sensitive data you hold.
## Byte-offset tests in pcap-filter Beyond host and port primitives, tcpdump's filter language can test raw header bytes with a **packet data accessor**: ``` proto [ expr : size ] ``` `proto` names the layer the offset is relative to (`ether`, `ip`, `ip6`, `tcp`, `udp`, `icmp` and others), `expr` is the byte offset and `size` is 1, 2 or 4 bytes, defaulting to 1. The result is an unsigned integer that you can combine with `+ - * / % & | ^ << >>` and compare with `> < >= <= = == !=`. So `tcp[13]` is the 14th byte of the TCP header, the one holding the control flags. ## Named offsets and values You do not have to remember the numbers. pcap-filter defines **`tcpflags`** as the offset of that byte and names each bit: | Name | Value | Bit | |---|---|---| | `tcp-fin` | 0x01 | FIN | | `tcp-syn` | 0x02 | SYN | | `tcp-rst` | 0x04 | RST | | `tcp-push` | 0x08 | PSH | | `tcp-ack` | 0x10 | ACK | | `tcp-urg` | 0x20 | URG | | `tcp-ece` | 0x40 | ECE | | `tcp-cwr` | 0x80 | CWR | `tcp-ece` and `tcp-cwr` arrived in libpcap 1.9.0; the others are older. Note that the PSH bit is named `tcp-push`. What each bit means for the connection is TCP's business; for filtering, what matters is which bits a given segment has set. ## Building the SYN-only filter The first segment of a handshake has SYN set and ACK clear. The reply has both. That gives the pattern **mask, then compare**: ``` tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn ``` 1. `tcp-syn|tcp-ack` is 0x12, a mask covering the two bits that distinguish the cases. 2. `tcp[tcpflags] & 0x12` keeps only those two bits of the flags byte. 3. `== tcp-syn` requires the result to be exactly 0x02: SYN on, ACK off. In pcap-filter's grammar, arithmetic and bitwise operators form the operands of a relation, so `&` binds tighter than `==`. That is the reverse of C, where `a & b == c` means `a & (b == c)`; here `tcp[13] & 0x12 == 2` means `(tcp[13] & 0x12) == 2`. Common wrong versions and why they fail: - `tcp[tcpflags] & tcp-syn != 0` - true for any segment with SYN set, so the SYN-ACK matches too. - `tcp[tcpflags] == tcp-syn` (or `tcp[13] == 2`) - demands that **no other bit** is set. A client that negotiates ECN sends its SYN with ECE and CWR set as well, making the byte 0xc2, so those SYNs are silently missed. - `tcp[tcpflags] & tcp-ack == 0` - matches every segment without ACK, not just SYNs. ## Related filters built the same way - **SYN-ACKs only:** `tcp[tcpflags] & (tcp-syn|tcp-ack) == (tcp-syn|tcp-ack)`. - **Resets:** `tcp[tcpflags] & tcp-rst != 0`; RST with ACK both set is `tcp[tcpflags] & (tcp-rst|tcp-ack) == (tcp-rst|tcp-ack)`. - **Start and end of every connection:** `tcp[tcpflags] & (tcp-syn|tcp-fin) != 0`. - **Segments with PSH set:** `tcp[tcpflags] & tcp-push != 0`. Combine them with ordinary primitives: `'tcp dst port 443 and tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'` shows new connection attempts to a TLS service and nothing else, which is a cheap way to count attempts per second during an incident without capturing payload. ## Quoting and scope caveats - Quote the expression in **single quotes**: `&`, `|` and parentheses are all shell metacharacters. - The `tcp[...]` accessor applies only to IPv4 packets that are unfragmented or the first fragment, so `tcp[0]` always means the first byte of the TCP header. On a dual-stack host, IPv6 SYNs need a different expression indexed from the IPv6 header. - `tcpflags` names byte 13 only. Any flag bit carried in byte 12 is outside that byte and needs its own `tcp[12]` test. ## Checking the compiled test Compiling the filter with `tcpdump -d` shows what the byte test became. On Ethernet the program first checks for an IPv4 EtherType, the TCP protocol number and a zero fragment offset, loads the IPv4 header length into the X register, and then loads the flags byte at `x + 27` (the 14-byte Ethernet header plus byte 13 of TCP). After that comes an `and` with 0x12 and a comparison with 0x2. If you see a single-bit `jset` instead, you compiled the SYN-bit-only version.
- How would you change the tcpdump SYN-only filter to show only SYN-ACKs?Keep the same mask and compare against both bits: `tcp[tcpflags] & (tcp-syn|tcp-ack) == (tcp-syn|tcp-ack)`. The mask isolates SYN and ACK; requiring the result to equal 0x12 means both are set, which is the reply to an initial SYN.
- Why does `tcp[13] & 0x12 == 2` not need parentheses around the `&` in a tcpdump filter?In pcap-filter's grammar the bitwise and arithmetic operators build the two operands of a relation, and the relational operator joins them. So `&` is applied first and `==` compares the result. A C programmer would expect the opposite precedence, which is why the explicit form still appears in many scripts.
saying these in an interview costs you the question
- Uses tcp[tcpflags] & tcp-syn != 0 and expects no SYN-ACKs
- Writes tcp[13] == 2 and assumes every initial SYN matches
- Reads tcp[13] & 0x12 == 2 with C operator precedence
- Uses tcp[tcpflags] & tcp-ack == 0 expecting only initial SYNs
- Leaves the & and | in the filter unquoted