Restore Conntrack DSCP and Marks with tc-ctinfo
A firewall rule stamped a DSCP or a mark onto the conntrack entry, but the packet crossing an interface later has lost it. tc-ctinfo reads that conntrack mark back and restores either the packet DSCP field, the packet mark, or both. The examples use the locally installed iproute2 package version 6.1.0-1ubuntu6.4 and its tc version 6.1.0.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 20 minutes if the filter already exists, or longer if you also need an IFB and a qdisc.
- Root privileges for changes to traffic control and for reading some live filter state.
- An existing conntrack-marking policy.
ctinforetrieves information, but it does not create or set the conntrack mark.
Warning
The commands below alter packet handling. A bad mask can restore the wrong DSCP or mark, and an ingress redirect can affect every packet on an interface. Run them first on a maintenance path or test interface. Keep the exact filter command so that you can remove it again.
1. Check the installed action
Start with a read-only action-list query. This confirms that the installed binary can ask the kernel for ctinfo actions. An empty result is normal when no ctinfo action has been installed yet:
$ tc actions list action ctinfo
$ printf '%s\n' "$?"
0
The action syntax used below is:
ctinfo [dscp mask [statemask]] [cpmark [mask]] [zone ZONE] [CONTROL] [index <INDEX>]
The package's local manual describes two independent modes:
dscpcopies six DSCP bits from the 32-bit conntrack mark into the IPv4 or IPv6 diffserv field.cpmarkcopies the conntrack mark into the packet mark.
You can put both modes on one action.
Checkpoint
If the list command fails with an unknown action or a kernel error, stop here. The kernel and tc need matching support; adding more filter syntax will not repair a missing action.
2. Choose and document the mark layout
A mask is a bit position, not the DSCP value you want to restore. The following layout reserves bits 26 through 31 for DSCP and bit 24 as a restore flag:
conntrack mark: [ DSCP: 0xfc000000 ][ flag: 0x01000000 ][ other bits ]
DSCP mask: 0xfc000000
state mask: 0x01000000
The DSCP mask must contain six contiguous bits. The optional state mask selects a flag elsewhere in the mark. The action restores DSCP only when the conntrack mark ANDed with the state mask is non-zero. The two masks must not overlap.
Write the layout beside the firewall or nftables rule that sets the mark. If another rule uses any of these bits, do not reuse the example masks. An incorrectly chosen mask is usually a policy error, not a syntax error, so tc may accept it without warning.
3. Add a DSCP restore action
The usual ingress shape is a filter matching all traffic, followed by ctinfo. Replace eth0 with the interface whose packets need restoration. This command requires root:
# tc filter add dev eth0 ingress protocol all pref 10 u32 \
match u32 0 0 action \
ctinfo dscp 0xfc000000 0x01000000
Here the first hexadecimal value is the DSCP location and the second is the conditional state mask. The action reads conntrack for the packet and writes the restored DSCP into its IPv4 or IPv6 diffserv field. It does not redirect traffic or install a qdisc.
The manual's longer IFB example adds a separate mirred action to send ingress packets to an IFB, where a qdisc such as CAKE can classify them. Keep those jobs separate while testing: first prove that the DSCP counter increases, then add redirection if the design needs it.
Inspect the filter immediately:
# tc -s filter show dev eth0 ingress
filter parent ffff: ...
action order 1: ctinfo zone 0 ...
... dscp 0xfc000000 0x01000000 ... DSCP set 0 ... error 0 ...
The exact byte and packet totals vary. Look for the action, the masks, and the DSCP set and error counters.
Tip
A zero counter can mean that no matching traffic has arrived, no conntrack entry was found, or the state bit is not set. It does not prove the mask is correct.
4. Restore a packet mark when a classifier needs it
Add cpmark when later traffic-control classification reads the packet mark. With no mask, the complete 32-bit conntrack mark is copied:
# tc filter add dev eth0 ingress protocol all pref 20 u32 \
match u32 0 0 action \
ctinfo cpmark
To copy only selected bits, provide a mask. For example, this restores the low 16 bits and leaves the rest of the packet mark clear according to the action's masked result:
# tc filter add dev eth0 ingress protocol all pref 20 u32 \
match u32 0 0 action \
ctinfo cpmark 0x0000ffff
Do not assume that the optional value is a DSCP mask. After cpmark, it is the mask applied to the conntrack mark before the result is stored in the packet mark. If you need both operations on one filter, use one action invocation with both modes:
# tc filter add dev eth0 ingress protocol all pref 10 u32 \
match u32 0 0 action \
ctinfo dscp 0xfc000000 0x01000000 cpmark 0x0000ffff
5. Make traversal and zones explicit
The action can select a conntrack zone with zone. The documented default is zone 0; specify another zone when the marking rules use one:
# tc filter add dev eth0 ingress protocol all pref 10 u32 \
match u32 0 0 action \
ctinfo zone 12 dscp 0xfc000000 0x01000000 pipe
zone is a 16-bit unsigned decimal value. It is not the hexadecimal mark value, and using the wrong zone makes a valid action look ineffective.
Use a control keyword when the next step matters:
pipecontinues with the next action on the same filter.continuecontinues with the next filter.reclassifystarts the current filter list again.droporshotdrops the packet.passfinishes classification and returns to the qdisc. The local manual documents this as the default.
Writing the intended control explicitly makes a multi-action filter easier to review.
6. Verify traffic, then remove the test rule
Generate one known flow that should have a conntrack entry and a mark, then inspect the counters again:
# tc -s filter show dev eth0 ingress
# tc -s filter show dev eth0 ingress | grep -E 'ctinfo|DSCP set|CPMARK set|error'
On the installed implementation, DSCP set counts packets whose DSCP was restored, CPMARK set counts mark copies, and the DSCP error count covers an unwritable destination field. These are action counters, not proof that a particular application flow used the intended mark. Check the packet and conntrack policy separately.
Before removing anything, record the complete filter listing and identify the preference used by your test. The matching delete command is:
# tc filter del dev eth0 ingress pref 10
# tc filter del dev eth0 ingress pref 20
Only delete preferences you added. If another filter shares that preference, use the full filter specification or restore the saved configuration instead of guessing. Verify that the temporary action is gone:
# tc -s filter show dev eth0 ingress | grep ctinfo
# printf '%s\n' "$?"
1
Done means
- Action available.
tc actions list action ctinfosucceeds on the installed host. - Mask is clean. Your DSCP mask contains six intended contiguous bits, and any state mask is separate.
- Zone matches policy. The conntrack zone matches the marking policy.
- Counters moved.
tc -s filter showshows the expected action and counters move for a known test flow. - Cleanup verified. The saved delete command removes the test filter without disturbing neighbouring rules.