Send a time-critical packet a few microseconds late and the whole point of time-sensitive networking is gone. Earliest TxTime First (ETF) holds packets until a scheduled transmission time, using timestamps supplied through Linux's SO_TXTIME socket interface. This guide installs ETF below an mqprio traffic class, inspects the resulting configuration, and removes it safely. Allow about twenty minutes for the queueing setup and verification, plus time to confirm that your NIC and application support the timing mode you need.
This guide describes the tc from iproute2 6.1.0, package version 6.1.0-1ubuntu6.4 on the reference system. The installed manual page is dated 5 July 2018. Hardware offload support and application APIs can differ across kernels, drivers and network cards, so treat the commands as a controlled starting point rather than a guarantee of wire timing.
Read-only inspection does not normally need elevated privileges. Choose the interface that will carry the time-sensitive traffic and verify that the installed utility understands the qdisc command:
$ command -v tc
/usr/sbin/tc
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0
$ ip link show dev eth0
Replace eth0 with the real interface. Do not guess it from an old configuration file. The following changes replace queueing configuration and may alter packet scheduling immediately, so use a maintenance window or a test interface. Have console access if this is a remote host.
Record the existing root and class hierarchy. This is an ordinary inspection command, but save the output somewhere you can read during recovery:
$ tc -s qdisc show dev eth0
$ tc -s class show dev eth0
The ETF manual expects ETF to sit below another qdisc that maps traffic into classes. mqprio is one example. If the interface already has a vendor, container or service-managed hierarchy, do not replace it from this guide. First identify which component owns it.
Checkpoint: you should know the interface name, whether a root mqprio hierarchy already exists, and how to restore the recorded configuration.
The example below creates three traffic classes and three hardware queue groups, then maps priorities into those classes. It requires root because it changes the live network device:
$ sudo tc qdisc add dev eth0 handle 100: parent root mqprio num_tc 3 \
map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \
queues 1@0 1@1 2@2 \
hw 0
Here, hw 0 keeps the class arrangement in software. The class identifiers are 100:1, 100:2 and 100:3. The mapping and queue counts are hardware-dependent; use values suitable for the selected interface instead of copying this layout blindly.
Verify the hierarchy before adding ETF:
$ sudo tc -s qdisc show dev eth0
$ sudo tc class show dev eth0
If the add command fails, do not continue to the ETF step. A partial or incompatible hierarchy needs to be diagnosed against the current driver and queue count.
Attach ETF to class 100:1 with the same clock that the sending sockets will use. The delta value is in nanoseconds; 300000 is 300 microseconds:
$ sudo tc qdisc replace dev eth0 parent 100:1 etf \
clockid CLOCK_TAI delta 300000
replace is useful while iterating because it creates the qdisc if absent and updates it if present. A delta gives the scheduler a lead time for waking before the packet timestamp. It is a timing margin, not a bandwidth limit, and the right value depends on scheduler latency and the system load.
ETF rejects packets whose transmission timestamp is in the past, and it can reject packets that expire while waiting. It also checks that packets come from sockets using SO_TXTIME, unless the qdisc is configured to skip that socket check. A normal application must therefore use the socket API described by socket(7), including the matching clock and per-packet control message.
Inspect the parent and the ETF options:
$ sudo tc -s qdisc show dev eth0
qdisc mqprio 100: root ...
qdisc etf ... parent 100:1 clockid CLOCK_TAI delta 300000
The exact statistics and formatting vary. Confirm that ETF is attached below the intended class and that the displayed clock and delta match the application. Then run the actual sender's smallest safe test. A successful tc command only proves that the qdisc was accepted; it does not prove that the NIC transmitted at the requested instant.
Tip: the default mode is strict transmission time. Adding deadline_mode changes the meaning: ETF treats the timestamp as a deadline and sets the packet timestamp to the current time as it dequeues it. Use that only when the sender and receiver have been designed for deadline semantics.
Only request offload when the NIC and driver document support for time-based transmission arbitration. The manpage calls this Launch Time or Time-Based Scheduling. The command is still privileged and can fail when the device cannot provide the feature:
$ sudo tc qdisc replace dev eth0 parent 100:1 etf \
clockid CLOCK_TAI delta 300000 offload
Check the command's error and inspect the qdisc afterwards. Do not interpret an accepted configuration as proof of hardware launch timing. If offload is unsupported, remove offload and use the software configuration, or stop and select hardware known to support the required feature.
ETF itself can be removed without deleting the parent hierarchy:
$ sudo tc qdisc del dev eth0 parent 100:1
When the entire hierarchy was created only for this test, remove the root mqprio qdisc as well:
$ sudo tc qdisc del dev eth0 root
$ sudo tc -s qdisc show dev eth0
That last command may show the interface's default queueing discipline. If another service owned the original root qdisc, restore its documented configuration instead of running the delete command. Keep the captured pre-change output until ordinary traffic has been tested.
clockid.delta is nanoseconds, not microseconds. Write the unit beside values in change records.tc -s qdisc show dev INTERFACE shows ETF below the intended mqprio class.clockid matches the sender's SO_TXTIME timestamps.