In my environment, I use tc to ensure VoIP traffic gets low-latency treatment across Linux routers and gateways. The core idea is marking packets with DSCP EF (Expedited Forwarding) so they jump the queue. First, I classify VoIP traffic—usually UDP ports 5060 for SIP and a range like 10000-20000 for RTP—then apply the EF mark. After that, I shape outgoing traffic on the WAN interface using HTB to give the EF class strict priority.
Marking VoIP Packets with DSCP EF
I start by identifying VoIP flows. For SIP signaling, I match port 5060. For media, I match the RTP port range used by our PBX. Using iptables in the mangle table, I set the DSCP field to 0x2e (EF). Here’s the rule set I apply:
# SIP signaling
iptables -t mangle -A POSTROUTING -p udp --dport 5060 -j DSCP --set-dscp-class EF
# RTP media range
iptables -t mangle -A POSTROUTING -p udp --dport 10000:20000 -j DSCP --set-dscp-class EF
After applying, I verify with tcpdump -vv -i eth0—the DSCP field should show 0x2e in the IP header. If not, check table and chain placement; POSTROUTING works for locally generated traffic, but for forwarding, use FORWARD or adjust based on routing.
Configuring the EF Queue with HTB
Marking alone doesn’t prioritize; I need a queuing discipline that honors DSCP. I use HTB on the egress interface (e.g., eth0) with three classes: high priority for EF, medium for bulk, and low for best-effort. The EF class gets a guaranteed minimum and can burst if bandwidth is free.
First, set the root qdisc:
tc qdisc add dev eth0 root handle 1: htb default 20
Then create classes. I shape to 90% of my WAN speed to avoid ISP drops—say 9mbit on a 10mbit link:
# Parent class
tc class add dev eth0 parent 1: classid 1:1 htb rate 9mbit ceil 9mbit
# EF class (10)
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 1mbit ceil 9mbit prio 0
# Bulk class (20) c class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 9mbit prio 1
# Best-effort (30) c class add dev eth0 parent 1:1 classid 1:30 htb rate 5mbit ceil 9mbit prio 2
Now attach sfq qdiscs to each class for fairness:
tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10
Finally, filter traffic by DSCP mark into the EF class:
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32
match ip dscp 0x2e 0xfc flowid 1:10
The prio 0 on the EF class ensures it’s serviced first. Lower prio number means higher priority in HTB.
Verifying SLA with Jitter Measurements
To validate, I generate synthetic VoIP traffic using pings or iperf3 with UDP, then measure jitter. I use tcpdump to capture RTP-like packets and calculate inter-arrival time variance with a small script:
# Capture UDP port 5004 (example RTP port)
tcpdump -i eth0 -w voip.pcap udp port 5004
Then analyze with tshark:
tshark -r voip.pcap -Y udp -T fields -e frame.time_relative | \
awk '{if (NR>1) print $1-prev; prev=$1}' | \
stats # using a simple awk-based min/max/avg/stddev
For VoIP, one-way jitter under 30ms is acceptable; under 10ms is good. I run this test before and after tc config to show improvement. If jitter spikes, I check if the EF class is starving—verify ceil rate and ensure no other class is borrowing excessively.
As I mentioned before in my post about IPv6 neighbor discovery, consistent monitoring catches regressions early. I log tc stats daily via cron and alert if EF class drop counter rises.
Common Pitfalls
A frequent mistake is marking in the wrong direction. DSCP must be set on packets leaving the LAN toward the WAN. If your router does NAT, mark before NAT or use -t mangle -A POSTROUTING on the correct interface. Another is forgetting that some ISPs remark DSCP at their edge—test with a VPS outside your AS to see if EF survives.
Also, HTB defaults to prio 0 only if explicitly set; otherwise, it uses classid as priority, which can invert expectations. Always define prio explicitly.
With this setup, I’ve seen SIP call setup times drop and MOS scores improve in monitoring tools. It’s not magic, but proper DSCP marking and queueing give VoIP a fighting chance on shared links.
Cover image: Unknown · CC0 (Openverse / kamu malı) · https://www.rawpixel.com/image/6038427/photo-image-public-domain-technology-line
