Home / Alt manpages / tc-police(8)

  • tc-police(8)
  • Admin command
  • linux

Rate-Limit Ingress Traffic with tc police

The word "police" makes people assume tc police throttles the sender. It doesn't: it only decides what happens to packets that have already arrived. This guide attaches a policing action to an ingress filter, caps matching traffic at a byte rate, inspects the installed rule, and removes it cleanly. The example uses the tc utility from iproute2 6.1.0 on Ubuntu and drops packets above 1 Mbit/s. Allow about 10 minutes, plus time to identify the correct interface.

Before you start

You need iproute2, a shell on the Linux host, and the name of the interface whose received traffic you want to police. Commands that change traffic control state need root privileges, so the examples use sudo; reading the current configuration does not.

Ingress policing is a limit applied as packets arrive, it does not make the sender transmit more slowly. Dropping excess packets can affect downloads, connections and monitoring, so use a test interface or a maintenance window when the traffic actually matters.

Set an obvious placeholder before copying the commands below. Do not leave YOUR_INTERFACE in a command you actually run.

$ ip -br link
$ DEV='YOUR_INTERFACE'
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0

1. Check for an existing ingress qdisc

Inspect the interface first. An ingress qdisc may already have filters attached, and replacing someone else's rule is an operational change you should understand before you make it.

$ tc qdisc show dev "$DEV"
qdisc noqueue 0: root refcnt 2

Output varies. If an ingress qdisc is already present, check its filters before continuing:

$ tc filter show dev "$DEV" ingress

Stop if you see rules you don't recognise. The cleanup command later removes the whole ingress qdisc, including filters that belong to someone else's work.

2. Add the ingress hook

This creates the hook that can hold an ingress filter. It changes live packet handling, though the qdisc alone doesn't impose a rate limit yet.

$ sudo tc qdisc add dev "$DEV" handle ffff: ingress

Verify it exists:

$ tc qdisc show dev "$DEV"
qdisc ingress ffff: parent ffff:fff1 ----------------

If the command says the qdisc already exists, go back to step 1. Do not blindly delete it, an existing ingress qdisc may be shared by several filters.

3. Add a 1 Mbit/s drop policy

The filter below uses u32 with a zero mask, which matches every packet. Its police action permits up to 1 Mbit/s with a 100 kB burst. Packets that exceed the rate are reclassified by default, so you need conform-exceed drop/ok if you actually want an immediate drop.

$ sudo tc filter add dev "$DEV" parent ffff: protocol ip prio 10 u32 \
    match u32 0 0 \
    action police rate 1mbit burst 100k \
    conform-exceed drop/ok

ok is the action for conforming packets; the slash separates the exceed action from the conform action. Leave this option out and conforming packets get accepted while exceeding packets get reclassified, which is rarely the result people expect from the word "drop".

4. Inspect and test the rule

Read the filter back before generating any traffic. Output format is version-dependent, but it should show a police action with the configured rate, burst and counters.

$ tc filter show dev "$DEV" ingress
filter protocol ip pref 10 u32 chain 0
filter protocol ip pref 10 u32 chain 0 fh 800: ht divisor 1
 police rate 1Mbit burst 100Kb conform-exceed drop/ok
  action order 1: police ...

Don't treat the sample lines as byte-for-byte output, just look for the rule at priority 10 and a police action. Generate a known amount of traffic from a controlled peer, run the show command again, and check whether the action counters actually increase. A counter stuck at zero usually means the filter isn't matching the traffic you expected, the traffic is on another protocol family, or it's arriving on a different interface.

For IPv6 traffic, the example's protocol ip selector isn't enough on its own. Add a separate rule with the right protocol selector, and verify that syntax on the target iproute2 build before touching a production interface. The police action itself can measure bytes or packets, but each filter still decides what reaches it in the first place.

5. Choose a different exceed action

Dropping is only one option. reclassify sends an exceeding packet back to filter processing as non-matching, continue moves on to the next action, pipe passes to the next action in the chain, and goto chain N jumps to another filter chain. These are useful when policing is one part of a larger policy, but they make the final outcome depend on whatever filters and actions come after.

You can also police packets instead of bytes. That's a separate token bucket, and needs pkt_rate and pkt_burst:

$ sudo tc filter add dev "$DEV" parent ffff: protocol ip prio 20 u32 \
    match u32 0 0 \
    action police pkt_rate 1000 pkt_burst 200 \
    conform-exceed drop/ok

At least one of rate and pkt_rate is required. A byte limit and a packet limit in separate filters can interact, so keep the test narrow and check both rules.

6. Remove the example safely

Warning

Removal is destructive to the whole ingress configuration. The command below removes the ingress qdisc and every filter attached to it, not just the example rule. Only run it once you've confirmed the qdisc holds no other required policy.

$ tc filter show dev "$DEV" ingress
$ sudo tc qdisc del dev "$DEV" ingress
$ tc qdisc show dev "$DEV"

If you need to keep other ingress filters, remove just the specific one by its priority and protocol after checking the displayed rule, leaving the qdisc in place:

$ sudo tc filter del dev "$DEV" parent ffff: protocol ip prio 10 u32
$ tc filter show dev "$DEV" ingress

If deletion says the qdisc or filter is missing, check the current state first, a network manager or another administrator may have already changed it.

Common traps

  • Wrong interface: ip -br link shows names, but a bridge, bond or virtual machine may mean the physical interface isn't where packets are actually counted.
  • Wrong unit: 1mbit is a rate in bits per second. burst 100k is a size in bytes. A burst is not an additional sustained bandwidth allowance.
  • Unexpected pass-through: without an explicit exceed action, the documented default for exceeding packets is reclassification, not dropping.
  • Too-small MTU assumption: the optional mtu value defaults to unlimited. Set it only if you understand how packets larger than that value should be treated.
  • Configuration lost on reboot: these commands alter the live kernel configuration. Arrange persistence through the network configuration system your host actually uses, and test its generated commands separately.

Done means

  • Interface and prior state checked: the intended interface and existing ingress rules were reviewed first.
  • Filter matches your intent: it shows the intended rate, burst and exceed action.
  • Counters proved it: controlled traffic increments the expected police counters.
  • Cleanup decided: you know whether the limit is temporary, and you've either removed the example or recorded the supported persistence method.