Use tc-stab to Account for Link-Layer Packet Size

A shaped link that ignores Ethernet or ATM framing overhead will always ship less throughput than the numbers promise, and tc-stab fixes that. You will finish with a tc qdisc configuration that accounts for framing overhead before the scheduler measures packets. That matters when a shaped link carries Ethernet framing or an ATM based access link: the IP packet length is not always the number of bytes that consume link capacity.

Allow about 20 minutes, plus time to confirm the overhead with the equipment or service documentation. You need the iproute2 package, an interface whose traffic you are prepared to reconfigure, and permission to change its traffic control settings. The installed reference here is iproute2 6.1.0, packaged as 6.1.0-1ubuntu6.4. The examples use the syntax documented by that version.

Warning: Replacing a root qdisc can change latency, queuing and throughput immediately. Run the change in a maintenance window, keep the existing configuration recorded, and make sure you have console access before changing a remote interface.

1. Confirm the installed command

Start with read-only checks. These do not need sudo:

$ command -v tc
/usr/sbin/tc
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0
$ tc qdisc show dev IFACE

Replace IFACE with the interface you will shape, such as ppp0 or eth0. Save the last command's output. A root qdisc may be omitted from the output if the interface has no explicit configuration, in which case the kernel is using its normal default.

Checkpoint: You know the exact interface and have a copy of its current qdisc output. Do not continue with a guessed interface name.

2. Choose the size-table model

stab creates a table of adjusted sizes. The table is calculated when tc builds the configuration, and the adjusted size is applied when a qdisc first enqueues the packet. It does not rewrite packets on the wire. It changes the size seen by the scheduler.

For Ethernet, use linklayer ethernet. This is effectively a one-to-one size mapping, but mpu must be greater than zero if you want an Ethernet size table. A packet below that minimum is charged at the MPU. The overhead value is still used even when no Ethernet table is created.

For ATM, use linklayer atm. adsl is also accepted and means the same ATM calculation. ATM uses 53-byte cells with 48 bytes of payload, padding the last cell. For example, 100 bytes of payload need three cells, or 159 bytes on the link. The value you choose for overhead must describe the headers that are actually outside the packet length seen by your shaping point.

Do not copy an overhead from a different DSL encapsulation. The installed manual lists different typical values for PPPoA, PPPoE, bridged traffic and IPoA, and distinguishes LLC from VC-Mux. If you shape traffic for another modem or router, an Ethernet header may already be included in the packet length; the manual says to compensate by subtracting 14 from those listed overheads in that arrangement.

3. Select table size and overhead

The two table-shape parameters have defaults that are easy to miss: mtu defaults to 2048 bytes and tsize to 512 slots. The slots are based on the smallest powers of two needed to cover the MTU, so neither value has to be a power of two. For an MTU of 1500 and 128 slots, the documented mapping is 0-16 bytes in slot 0, 17-32 in slot 1, and up to 2033-2048 in slot 127.

Use an explicit pair when you are matching a known link or want a configuration that is easy to audit. The following is an Ethernet example. The values are placeholders, not universal defaults:

mtu 1500 tsize 128 mpu 64 overhead 40 linklayer ethernet

Here, mpu 64 charges anything smaller than 64 bytes at the minimum, and overhead 40 adds 40 bytes before the table slot is selected. The number 40 is only suitable if it matches your actual encapsulation. A negative overhead is supported, but use it only when you have measured or documented why bytes already included in the packet length must be removed.

4. Add the table to a qdisc

stab is an argument to the qdisc command, not a separate persistent file. This example attaches it to a token bucket filter while setting a deliberately modest rate. It requires elevated privileges and changes the live root qdisc:

$ sudo tc qdisc replace dev IFACE root handle 1: tbf rate 10mbit burst 32kbit latency 50ms stab mtu 1500 tsize 128 mpu 64 overhead 40 linklayer ethernet

The command uses replace, so it replaces an existing root qdisc. That is convenient for a planned change, but it is not an undo operation. If your actual scheduler is HTB, HFSC, TAPRIO or another qdisc, keep its documented parameters and add the same stab clause where that qdisc accepts generic size-table arguments. The manpage's syntax is intentionally shown as tc qdisc add ... stab; the qdisc-specific part still belongs to the qdisc you chose.

For an ATM access link, the tail of the command might instead be:

stab mtu 1500 tsize 128 mpu 64 overhead 14 linklayer atm

Do not run that example until 14 has been checked against the exact encapsulation. ATM rounding and protocol headers are separate effects, so adding a familiar Ethernet value to an ATM table can materially mischarge packets.

5. Verify the live configuration

Read the qdisc back immediately. This is an ordinary command and normally needs no elevation:

$ tc -d qdisc show dev IFACE
qdisc tbf 1: root ...
$ tc qdisc show dev IFACE

Formatting varies with the qdisc and iproute2 build. Confirm that the expected root qdisc is present, that the rate and latency are the values you intended, and that no unrelated qdisc disappeared. If your build does not print every size-table field, that is not proof that the table was ignored. Compare the command you ran with the qdisc's detailed output and inspect traffic behaviour with your normal monitoring.

For a more controlled check, send a known flow across the shaped interface and compare the observed rate with the configured rate. Test small and near-MTU packets if MPU or ATM rounding is the reason for the table. Keep in mind that a successful tc command verifies configuration, not that a remote modem applies the same framing assumptions.

Checkpoint: The qdisc read-back matches the intended interface, scheduler, rate and latency, and your test traffic behaves as expected.

6. Avoid the common packet-size trap

Network-card offloads can make a slow-link test misleading. TSO and GSO may present large TCP segments to traffic shaping, so the scheduler can perform size-table calculations on sizes that are not the wire packets you expected. Check the interface's offload state:

$ ethtool -k IFACE | grep -E 'tso|gso'
        tcp-segmentation-offload: on
        generic-segmentation-offload: on

The output is host-specific. If offloads are distorting a slow-uplink test, disabling them requires elevated privileges and can affect performance. Change only the features you have identified, record the original state, and test again:

$ sudo ethtool -K IFACE tso off gso off
$ ethtool -k IFACE | grep -E 'tso|gso'

Undo that temporary test with the recorded settings, for example:

$ sudo ethtool -K IFACE tso on gso on

Some drivers expose additional segmentation or receive-offload features, and a feature may be fixed by hardware. Treat the read-back from ethtool -k as authoritative for this host rather than assuming every NIC supports the same switches.

7. Restore the previous qdisc

If the change causes loss or unexpected latency, restore the configuration you recorded in step 1. This requires elevated privileges. For example, removing the temporary root qdisc returns the interface to the kernel's default root behaviour:

$ sudo tc qdisc del dev IFACE root
$ tc qdisc show dev IFACE

That command also removes any classes and filters below the root qdisc. If the old setup was more complex, restore its saved commands instead of deleting it. A deletion is disruptive and does not reconstruct an earlier HTB, HFSC or filter hierarchy.

Done means