Build Safe tc-ematch Filters with basic

An extended-match expression lets an iproute2 basic filter test packet bytes or firewall state directly, no dedicated classifier needed. Here you will build and inspect one, then remove the test rule cleanly. The useful result is a filter expression you can adapt to a real device, with its packet layer, offset and comparison made explicit.

Allow 15 to 30 minutes. You need the iproute2 package and a device with a suitable traffic-control hook. The examples below were checked against iproute2 6.1.0-1ubuntu6.4, whose tc utility reports iproute2 6.1.0.

1. Learn the expression shape

An ematch expression is a match module followed by parentheses containing its arguments. Expressions can be joined with and or or, and grouped with parentheses. Prefix a term with not to invert it. The expression is passed after match in a basic filter:

tc filter add dev DEVICE ingress basic match 'EXPRESSION' classid 1:10

Replace DEVICE with an interface you own and 1:10 with a class that really exists if you are classifying packets. The command normally needs root privileges, for example through sudo. Do not paste a placeholder device or class ID into a production command.

Tip: the shell quotes matter. The ematch grammar uses parentheses, and an unquoted expression lets the shell interpret them before tc sees them. Keep the complete expression in single quotes unless it contains a value that genuinely needs shell expansion.

2. List the metadata available on this host

Metadata matches compare packet or socket state rather than bytes at a packet offset. The manual documents a listing query. Run it against a disposable or already approved traffic-control hook:

$ sudo tc filter add dev DEVICE ingress basic match 'meta(list)'

The command prints a table of attributes such as random, loadavg_1, nf_mark, vlan, socket buffer values and traffic-control index data. On the installed utility, the listing also includes loadavg_5 and loadavg_15. That is a version-specific detail: inspect the output on the target host instead of assuming that a metadata name available here exists everywhere.

On this machine, the listing expression is parsed by tc but the attempted filter is rejected by the kernel with an ematch error. That makes it useful as a discovery probe, not a successful installation example. Remove any rule if your kernel accepts it:

$ sudo tc filter del dev DEVICE ingress

Checkpoint: you have a local list of metadata names, and tc filter show dev DEVICE ingress shows no temporary rule before continuing.

3. Add a byte comparison on a test hook

The cmp module performs an arithmetic comparison against packet data. Its form is cmp(ALIGN at OFFSET [layer LAYER] [mask MASK] [trans] OP VALUE).

For example, this is the manual's representative shape for a comparison at numeric layer 2:

$ sudo tc filter add dev DEVICE ingress basic \
    match 'cmp(u16 at 3 layer 2 mask 0xff00 gt 20)' \
    classid 1:10

Read that expression literally: read an unsigned 16-bit value at offset 3, use layer 2 as the base, mask it with 0xff00, and test whether the result is greater than 20. The offset is not a universal packet position. It depends on the selected layer and the packet format, so verify it against the traffic you expect before relying on the match.

Check the result immediately:

$ sudo tc filter show dev DEVICE ingress

If the expression is accepted, the filter listing should contain a basic filter and the ematch details. If you see an error such as Illegal "ematch" or a parser message, do not keep changing offsets at random. Check the exact grammar, the installed kernel support and the device hook first.

4. Combine matches without losing the boundary

Use a second term when one condition is not enough. This example combines a packet comparison with metadata:

$ sudo tc filter add dev DEVICE ingress basic \
    match 'cmp(u16 at 3 layer 2 mask 0xff00 gt 20) and meta(nf_mark gt 24)' \
    classid 1:10

Use the metadata spelling printed by meta(list) on the target host. The older manual's prose and examples use both nf_mark-style metadata output and an nfmark example, while this installed utility lists nf_mark. Treat the local listing as authoritative for the machine where the rule will run.

To group alternatives, write the parentheses inside the shell quotes:

$ sudo tc filter add dev DEVICE ingress basic \
    match '(meta(vlan gt 0) or meta(nf_mark gt 24))' \
    classid 1:10

Do not confuse an ematch group with the shell's syntax. The outer single quotes protect both the group and its operators.

5. Keep ipset and ipt matches in their lane

The ipset module tests membership in an existing ipset. A source-address example is:

$ sudo tc filter add dev DEVICE ingress basic \
    match 'ipset(bulk src)' classid 1:10

The set must already exist and its flags must match the traffic you intend to test. The ematch does not create the set. The ipt module delegates to an xtables match, for example:

$ sudo tc filter add dev DEVICE ingress basic \
    match 'ipt(-m policy --dir in --pol ipsec --reqid 1)' \
    classid 1:10

Warning: these are security-sensitive boundaries: an incorrect set, policy direction or interface can classify the wrong packets. Verify the supporting ipset or firewall state separately, and do not use a broad production device while experimenting.

6. Remove the test rule

Filter changes affect live traffic. Before deleting anything, list the hook and identify the filter you added:

$ sudo tc filter show dev DEVICE ingress

For an isolated test hook, remove the filters from that hook:

$ sudo tc filter del dev DEVICE ingress

Warning: this is destructive to every filter attached to that device and hook, not just the one you remember adding. On a shared production hook, use the filter's exact protocol, parent and preference as shown by tc filter show, then delete only that identified rule. Save the existing configuration or use your normal configuration manager before making changes; tc does not provide an undo history.

Done means