iproute2 ile Linux’ta Trafik Şekillendirme ve Bant Genişliği Sınırlandırma (tc Komutu)
Ağ yönetiminde en çok ihmal edilen konulardan biri trafik şekillendirme. Çoğu sysadmin firewall kurallarına, routing tablolarına saatler harcar ama bant genişliği kontrolüne gelince “yeter ki çalışsın” moduna geçer. Oysa üretim ortamlarında bir backup işleminin tüm ağı yutması, bir müşterinin diğerlerinin bant genişliğini tüketmesi ya da kritik servislerin yoğun trafik dönemlerinde yavaşlaması gerçek sorunlar. Bu yazıda Linux’un trafik kontrolü alt sistemini, tc komutunu ve pratikte nasıl kullanacağınızı elimden geldiğince gerçekçi senaryolarla anlatmaya çalışacağım.
Linux Trafik Kontrolünün Temel Mantığı
Linux’ta trafik şekillendirme, kernel’in içinde yaşayan Traffic Control (TC) alt sistemi üzerinden çalışır. iproute2 paketinin bir parçası olan tc komutu bu sisteme kullanıcı alanından erişim sağlar.
İşin merkezinde qdisc (queueing discipline) kavramı var. Basitçe söylemek gerekirse: ağ arayüzünden çıkacak paketlerin nasıl sıraya alınacağını, hangisinin önce gönderileceğini ve hangisinin geciktirileceğini ya da atılacağını belirleyen kurallar bütünü. Her ağ arayüzünün hem çıkış (egress) hem de giriş (ingress) yönünde bir qdisc’i var.
Üç temel yapı taşı şunlar:
- qdisc: Kuyruk disiplini. Paketlerin nasıl işleneceğini tanımlar.
- class: qdisc içindeki alt bölümler. Farklı trafik türlerine farklı davranışlar atamak için kullanılır.
- filter: Paketleri sınıflandırıp ilgili class’a yönlendiren kurallar.
Önemli bir nokta: tc varsayılan olarak yalnızca egress (çıkış) trafiğini şekillendirebilir. Giriş trafiğini sınırlandırmak için ingress qdisc ve IFB (Intermediate Functional Block) gibi numaralara başvurmak gerekir, bunu da yazının ilerleyen kısımlarında göreceğiz.
Ortam Hazırlığı
Başlamadan önce sistemde iproute2 paketinin kurulu olduğunu doğrulayın:
tc -Version
# ya da
ip -Version
Çoğu modern Linux dağıtımında bu paket varsayılan olarak geliyor. Gelmiyorsa:
# Debian/Ubuntu
apt install iproute2
# RHEL/CentOS/Rocky
dnf install iproute
Mevcut arayüzlerin qdisc durumunu görmek için:
tc qdisc show
# ya da belirli bir arayüz için
tc qdisc show dev eth0
Temiz bir sistemde genellikle pfifo_fast veya mq gibi varsayılan qdisc’leri görürsünüz.
Senaryo 1: Basit Bant Genişliği Sınırı (TBF)
Diyelim ki bir dosya sunucunuz var ve gece yarısı backup işlemleri tüm hat kapasitesini tüketiyor. Basit bir çözüm: tbf (Token Bucket Filter) qdisc kullanarak çıkış trafiğini sınırlandırmak.
# eth0 üzerinde çıkış trafiğini 50 Mbit/s ile sınırla
tc qdisc add dev eth0 root tbf
rate 50mbit
burst 32kbit
latency 400ms
Parametrelerin anlamları:
- rate: Hedef bant genişliği.
50mbit,100kbit,1gbitgibi değerler alır. - burst: Token bucket’ın anlık taşıyabileceği maksimum veri miktarı. Çok düşük tutarsanız CPU yükü artar.
- latency: Bir paketin kuyrukta bekleyebileceği maksimum süre. Bu süreyi aşan paketler atılır.
Kuralı kaldırmak için:
tc qdisc del dev eth0 root
Mevcut durumu kontrol etmek için:
tc qdisc show dev eth0
tc -s qdisc show dev eth0 # istatistiklerle birlikte
TBF güzel ve basit ama sınırlı. Tüm trafiği tek bir kova içine atıyor. Farklı servislere farklı öncelikler vermek istiyorsanız HTB’ye ihtiyacınız var.
Senaryo 2: HTB ile Hiyerarşik Bant Genişliği Yönetimi
HTB (Hierarchical Token Bucket), üretim ortamlarında en çok tercih edilen qdisc türü. Trafiği sınıflara ayırmanıza, her sınıfa garanti ve maksimum bant genişliği tanımlamanıza izin veriyor. Kullanılmayan bant genişliği de başka sınıflarla paylaşılabiliyor.
Şu senaryoyu ele alalım: 1 Gbit/s hat kapasiteli bir sunucuda şu trafikleri yönetmek istiyorsunuz:
- HTTP/HTTPS trafiği: Minimum 600 Mbit/s garanti, maksimum 900 Mbit/s
- Veritabanı replikasyon trafiği: Minimum 200 Mbit/s garanti, maksimum 400 Mbit/s
- Yedekleme trafiği: Minimum 50 Mbit/s garanti, maksimum 150 Mbit/s
# Önce mevcut kuralları temizle
tc qdisc del dev eth0 root 2>/dev/null
# Root HTB qdisc ekle
tc qdisc add dev eth0 root handle 1: htb default 30
# Toplam bant genişliği için ana sınıf
tc class add dev eth0 parent 1: classid 1:1 htb rate 1gbit burst 15k
# HTTP/HTTPS sınıfı (classid 1:10)
tc class add dev eth0 parent 1:1 classid 1:10 htb
rate 600mbit
ceil 900mbit
burst 15k
prio 1
# Veritabanı replikasyon sınıfı (classid 1:20)
tc class add dev eth0 parent 1:1 classid 1:20 htb
rate 200mbit
ceil 400mbit
burst 15k
prio 2
# Yedekleme sınıfı (classid 1:30) - default olarak işaretli
tc class add dev eth0 parent 1:1 classid 1:30 htb
rate 50mbit
ceil 150mbit
burst 15k
prio 3
Parametrelerin anlamları:
- handle: qdisc veya class’ın benzersiz tanımlayıcısı.
major:minorformatında. - rate: Garanti edilen minimum bant genişliği.
- ceil: Kullanılabilecek maksimum bant genişliği (burst kapasitesi dahil).
- burst: Anlık maksimum paket boyutu. Genellikle
rate/8000değeri iyi bir başlangıç noktasıdır. - prio: Öncelik değeri. Düşük değer daha yüksek öncelik anlamına gelir.
- default: Sınıflandırılamamış trafiklerin düşeceği class ID.
Şimdi her sınıfa bir yaprak qdisc ekleyelim (opsiyonel ama önerilen):
# Her sınıfa SFQ (Stochastic Fairness Queueing) ekle
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
SFQ, aynı sınıf içindeki akışlar arasında adil dağılım sağlar. perturb 10 her 10 saniyede bir hash tablosunu yeniler, bu da belirli akışların diğerlerini domine etmesini engeller.
Filtreleme: Trafiği Doğru Sınıfa Yönlendirmek
HTB sınıflarını tanımladık ama hangi paketin hangi sınıfa gideceğini henüz söylemedik. Bu iş filterların görevi:
# HTTP trafiğini (port 80 ve 443) 1:10 sınıfına yönlendir
tc filter add dev eth0 parent 1: protocol ip prio 1 u32
match ip dport 80 0xffff
flowid 1:10
tc filter add dev eth0 parent 1: protocol ip prio 1 u32
match ip dport 443 0xffff
flowid 1:10
# Veritabanı replikasyon trafiğini (PostgreSQL 5432) 1:20 sınıfına yönlendir
tc filter add dev eth0 parent 1: protocol ip prio 2 u32
match ip dport 5432 0xffff
flowid 1:20
# Belirli bir IP adresinden gelen trafiği yedekleme sınıfına yönlendir
tc filter add dev eth0 parent 1: protocol ip prio 3 u32
match ip dst 10.0.0.50/32
flowid 1:30
Tüm yapıyı bir arada görmek için:
tc -s class show dev eth0
tc filter show dev eth0
Senaryo 3: Ingress Trafiğini Sınırlandırma
Daha önce söylediğim gibi tc doğrudan giriş trafiğini şekillendiremez. Ama bir numara var: IFB (Intermediate Functional Block) sanal arayüzü. Giriş trafiğini IFB’ye yönlendirip orada egress olarak işleyebilirsiniz.
# IFB modülünü yükle
modprobe ifb
# IFB arayüzünü aktive et
ip link set dev ifb0 up
# eth0'ın ingress trafiğini ifb0'a yönlendir
tc qdisc add dev eth0 handle ffff: ingress
tc filter add dev eth0 parent ffff: protocol ip u32
match u32 0 0
action mirred egress redirect dev ifb0
# ifb0 üzerinde HTB ile giriş trafiğini sınırla
tc qdisc add dev ifb0 root handle 1: htb default 10
tc class add dev ifb0 parent 1: classid 1:1 htb rate 500mbit
tc class add dev ifb0 parent 1:1 classid 1:10 htb
rate 500mbit
ceil 500mbit
Bu yapıyla sunucuya gelen trafiği de kontrol altına alabilirsiniz. Özellikle ISP’den sabit hat alıp bant genişliğini çok kiracılı (multi-tenant) ortamlarda paylaştırmanız gereken durumlarda işe yarar.
Senaryo 4: DSCP ile QoS Markalaması
Kurumsal ağlarda genellikle paketler DSCP (Differentiated Services Code Point) değerleriyle işaretlenir. tc bu işaretlere göre filtreleme yapabilir:
# DSCP EF (Expedited Forwarding - VoIP trafiği) için öncelikli sınıf
tc filter add dev eth0 parent 1: protocol ip prio 1 u32
match ip tos 0xb8 0xfc
flowid 1:10
# DSCP AF31 (iş uygulamaları) için orta öncelikli sınıf
tc filter add dev eth0 parent 1: protocol ip prio 2 u32
match ip tos 0x68 0xfc
flowid 1:20
Trafiği ilerletmeden önce DSCP değeri atamak isterseniz tc ile birlikte iptables kullanabilirsiniz:
# SSH trafiğini DSCP AF21 ile işaretle
iptables -t mangle -A OUTPUT -p tcp --dport 22
-j DSCP --set-dscp-class AF21
İzleme ve Hata Ayıklama
Kurduğunuz kuralların gerçekten çalışıp çalışmadığını nasıl anlarsınız? İstatistiklere bakarak:
# Tüm sınıfların istatistiklerini göster
tc -s class show dev eth0
# Gerçek zamanlı izleme için watch ile kullan
watch -n 1 'tc -s class show dev eth0'
# qdisc istatistikleri (dropped paket sayısı önemli)
tc -s qdisc show dev eth0
tc -s çıktısında dikkat etmeniz gereken alanlar:
- Sent: Gönderilen toplam byte ve paket sayısı
- dropped: Atılan paket sayısı. Bu değer sürekli artıyorsa rate değeriniz çok düşük ya da burst değeriniz yetersiz.
- overlimits: Sınır aşım sayısı. Sıfır olması iyidir ama bazı durumlarda beklenen bir değer de olabilir.
- requeues: Yeniden kuyruğa alınan paketler.
Mevcut filtreler ve eşleşme sayıları için:
tc -s filter show dev eth0
Kalıcı Yapılandırma
tc komutlarıyla yaptığınız değişiklikler sistem yeniden başladığında kaybolur. Kalıcı hale getirmek için birkaç yöntem var.
Yöntem 1: Network interface script kullanımı
Debian/Ubuntu sistemlerde /etc/network/interfaces dosyasına post-up direktifi ekleyebilirsiniz:
# /etc/network/interfaces
auto eth0
iface eth0 inet static
address 192.168.1.10/24
gateway 192.168.1.1
post-up tc qdisc add dev eth0 root handle 1: htb default 30
post-up tc class add dev eth0 parent 1: classid 1:1 htb rate 1gbit
pre-down tc qdisc del dev eth0 root
Yöntem 2: Systemd service
Daha temiz bir yöntem, tüm tc komutlarını bir bash script’e toplayıp systemd servisi olarak çalıştırmak:
#!/bin/bash
# /usr/local/sbin/tc-setup.sh
DEV="eth0"
# Temizlik
tc qdisc del dev $DEV root 2>/dev/null
# Root qdisc
tc qdisc add dev $DEV root handle 1: htb default 30
# Sınıflar
tc class add dev $DEV parent 1: classid 1:1 htb rate 1gbit burst 15k
tc class add dev $DEV parent 1:1 classid 1:10 htb rate 600mbit ceil 900mbit burst 15k prio 1
tc class add dev $DEV parent 1:1 classid 1:20 htb rate 200mbit ceil 400mbit burst 15k prio 2
tc class add dev $DEV parent 1:1 classid 1:30 htb rate 50mbit ceil 150mbit burst 15k prio 3
# Yaprak qdisc'ler
tc qdisc add dev $DEV parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev $DEV parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev $DEV parent 1:30 handle 30: sfq perturb 10
# Filtreler
tc filter add dev $DEV parent 1: protocol ip prio 1 u32
match ip dport 80 0xffff flowid 1:10
tc filter add dev $DEV parent 1: protocol ip prio 1 u32
match ip dport 443 0xffff flowid 1:10
tc filter add dev $DEV parent 1: protocol ip prio 2 u32
match ip dport 5432 0xffff flowid 1:20
echo "TC rules applied successfully"
chmod +x /usr/local/sbin/tc-setup.sh
# Systemd unit dosyası
cat > /etc/systemd/system/tc-rules.service << 'EOF'
[Unit]
Description=Traffic Control Rules
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/tc-setup.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now tc-rules.service
Sık Yapılan Hatalar
Yıllar içinde gördüğüm yaygın hatalardan bahsetmek istiyorum:
Burst değerini çok düşük tutmak: burst değeri rate / 8 / 1000 bayttan düşük olmamalı. Örneğin 100 Mbit/s için en az 12500 byte (12.5k) burst gerekir. Düşük tutarsanız CPU interrupt’ları artar ve beklenmedik paket kayıpları yaşarsınız.
Default sınıfı unutmak: HTB’de default parametresini belirtmezseniz ya da yanlış bir class ID verirseniz, filtreyle eşleşmeyen tüm trafik root’a düşer ve şekillendirme dışında kalır. Her zaman bir default class tanımlayın.
Filtrelerin sırasına dikkat etmemek: prio değerleri filtre önceliğini belirler. Daha spesifik kuralları daha düşük prio değeriyle (yani daha yüksek öncelikle) tanımlayın.
Ingress sınırlamasını göz ardı etmek: Sadece egress’i sınırlandırmak çoğu durumda yeterli değildir, özellikle simetrik bant genişliğiniz varsa. IFB yöntemini göz ardı etmeyin.
Sonuç
tc ve iproute2’nin trafik şekillendirme kabiliyetleri son derece güçlü ama öğrenme eğrisi dik. Burada anlattıklarım bu dünyanın yalnızca bir bölümü. HFSC, CAKE, FQ-CoDel gibi daha gelişmiş qdisc’ler var, eBPF ile entegrasyon var, tüm bunlar ayrı birer yazı konusu.
Pratik tavsiyem: Önce TBF ile basit sınırlandırmayı deneyin, sonra HTB’ye geçin. Üretim ortamına uygulamadan önce test ortamında netem ile gecikme ve paket kaybı simüle edin, filterlarınızın doğru sınıfı yakaladığını doğrulayın. tc -s istatistiklerini düzenli takip etmeyi alışkanlık haline getirin.
Trafik şekillendirme doğru yapıldığında kritik servislerin yoğun saatlerde bile stabil kalmasını sağlar, “gece backup’ı ağı öldürdü” gibi sabah mesajlarından kurtarır ve çok kiracılı ortamlarda adaletli kaynak dağılımı sunar. Zahmetine değer.
