The display filter language
Comparison, logic, membership, and slices. The surprises are fields that occur twice, and fields that never share a frame.
Comparisons and logic
A display filter compares a field to a value: ==, !=, >, <, >=, <=. && and || combine tests, and ! negates one. Strings take contains and matches. contains is a case-sensitive substring. matches is a regular expression, which is the wrong tool until contains fails you.
Membership keeps a filter readable. tcp.port in {80, 443} is the same idea as tcp.port == 80 || tcp.port == 443. Use the form you will still understand in a week. Quote strings. Leave numbers unquoted.
tcp.port in {80, 443} && ip.addr == 10.8.0.40A field can be true twice in one frame
ip.addr occurs twice, once for the source and once for the destination. The expression ip.addr == 10.8.0.40 && ip.addr == 10.8.0.20 matches a frame that has one of those addresses on each side. It is the usual way to say “between these two hosts” without caring about direction. ip.src == 10.8.0.40 && ip.dst == 10.8.0.20 is the directional version.
The same trick fails across packets. http.request.method == "POST" && http.response.code == 200 does not mean “the POST that got a 200.” The request and the response are different frames, so the two fields are not true together. Filter to the request, note tcp.stream, and then read that stream. The stream index is how a multi-frame fact stays one fact.
Worked case: the POST, then its reply
The lab app on 10.8.0.20 received a login. You filter http.request.method == "POST" && ip.addr == 10.8.0.20 and find frame 900 in tcp.stream 14. The response is not in that row. You replace the filter with tcp.stream == 14 and read the next HTTP response in that stream. The status code sits on its own frame, a few packets later.
You also want every lab host that touched either port 80 or port 443. tcp.port in {80, 443} && ip.addr == 10.8.0.0/24 does that in one line. Click a surprising row and check the field, because a match on tcp.port is true for the source port or the destination port. An ephemeral source port of 443 is rare and worth a second look when it happens.
Slices when the dissector has no field
A slice such as tcp.payload[0:4] tests raw bytes. Reach for it when you know the offset and the dissector has not named the field. Prefer a named field when one exists. Named fields survive version changes in your notes better than offsets do.
When a filter returns nothing, the field may be absent rather than different. http.authorization with no comparison matches frames where that field exists. That presence test is how you find “did anyone send the header,” which is a different question from “did this value appear.”
- == and != compare one field to a value.
- && and || combine tests. Parentheses group them.
- ip.addr twice means the two ends of one frame.
- A request field and a response field need the stream, not one &&.
Diagram
One frame can hold a field twice. Two frames cannot share a single test.
- Compare
Compare a field to a literal. Quote strings and group the logic.
tcp.port in {80, 443} && ip.addr == 10.8.0.40 - Two ends
Repeating ip.addr matches the two addresses of a single frame.
- Two frames
A request and its response live in one tcp.stream, not in one row.
- Presence
A bare field name tests that the field exists.
http.authorization
How to say “these two”
Filter. ip.addr == 10.8.0.40 && ip.addr == 10.8.0.20
Holds. Each side of the same frame supplies one match.
Use. The conversation, in either direction.
Filter. http.request.method == "POST" && http.response.code == 200
Holds. Almost never. Those fields sit on different frames.
Use. Find the POST, then filter tcp.stream for that index.
Check yourself
1. Which filter selects packets between 10.8.0.40 and 10.8.0.20, either way?
2. You found a POST in tcp.stream 14 and need its HTTP status. What do you filter next?
3. Which filter finds frames that carry an HTTP Authorization header, whatever the value?