Pakety prichadzaju ale aplikacia timeoutuje: rp_filter pasca v Kubernetes
Asymetricke routovanie ma skoro priviedlo do sialenstva; vinnik bol rp_filter. “Pakety prichadzaju. Vidim ich v tcpdump. Ale aplikacia timeoutuje.” Toto bola najmatucejsia debugging session za posledne mesiace. Nastavovali sme hybridnu siet kde Kubernetes pody potrebovali komunikovat so sluzbami na externej sieti pripojenej cez sekundarny interface. Health checky fungovali v jednom smere ale zlyhavali v druhom.
Klucom bolo ze tcpdump na cielovom interface ukazoval prichadzajuce SYN pakety, ale aplikacny socket ich nikdy nedostal. Connection tracking neukazoval ziadne zaznamy. Akoby pakety prichadzali do kernelu a potom zmizli do prazdna predtym nez dosiahli aplikaciu.
Po hodinach debugovania som objavil ze problem bol rp_filter - Linux reverse path filtering. Ked pakety prichadzaju na interface, kernel kontroluje ci by bola zdrojova adresa routovatelna spat cez ten isty interface. Ak nie, paket je ticho zahodeny ako “martian” paket. Nase asymetricke routovanie (pakety prichadzali na eth1, ale navratova cesta cez eth0) bolo interpretovane ako spoofovany traffic.
Co robilo toto zvlast frustrujuce bolo ze standardne sietove debugovacie nastroje neodhalili problem. tcpdump funguje pred rp_filter, takze ukazuje pakety ktore budu zahodene. conntrack neukazuje nic pretoze paket sa nikdy nedostal ku connection trackingu.
Prostredie: Kubernetes 1.28+, multi-homed nody (viacero NIC), hybridne/on-prem siete, VPN spojenia
Pochopenie rp_filter
Co robi Reverse Path Filtering
Normalne symetricke routovanie (rp_filter prejde):
Klient (10.0.0.5)
|
| SYN paket prichadza na eth0
v
+-------------------+
| eth0 |
| (10.0.0.0/24) |
+-------------------+
|
| Kernel kontroluje: "Ak by som posielal paket NA 10.0.0.5,
| ktory interface by som pouzil?"
| Route table vracia: 10.0.0.0/24 → eth0 ✓
|
v
Paket akceptovany → Aplikacia ho dostane
Asymetricke routovanie (rp_filter ZAHODI paket):
Klient (10.0.0.5) → Router → Sekundarna siet
|
| SYN prichadza na eth1
v
+-------------------+ +-------------------+
| eth0 | | eth1 |
| (192.168.1.0/24) | | (10.100.0.0/24) |
| default route | | |
+-------------------+ +-------------------+
|
Kernel kontroluje: "Ak by som posielal paket NA 10.0.0.5,
ktory interface by som pouzil?"
Route table vracia: default route → eth0 ← INY!
|
v
PAKET ZAHODENY AKO MARTIAN!
(tcpdump ho videl, aplikacia nikdy)
Tri rp_filter mody
# Skontroluj aktualne nastavenie
cat /proc/sys/net/ipv4/conf/all/rp_filter
cat /proc/sys/net/ipv4/conf/eth0/rp_filter
# Mody:
# 0 = Ziadna validacia (vypnute) - pakety vzdy akceptovane
# 1 = Striktny mod (default na vela distro)
# Zdroj musi byt dostupny cez TEN ISTY interface
# 2 = Volny mod
# Zdroj musi byt dostupny cez AKYKOLVEK interface
Preco Kubernetes/CNI vytvara asymetricke routy
Bezne scenare kde rp_filter hryzne:
1. Multi-homed nody (VPN + primarna siet):
Pod → VPN gateway → Vzdialena sluzba
Navratovy traffic: Vzdialena → Primarny interface (ina cesta)
2. Externe load balancery s DSR (Direct Server Return):
Klient → LB → Node (eth0)
Odpoved: Node → Klient priamo (obchadzajuc LB)
Ak LB zmenil zdrojovu IP, navratova cesta sa lisi
3. CNI s viacerymi sietami (Multus):
Pod ma: eth0 (cluster siet), net1 (externa siet)
Traffic tecie asymetricky medzi sietami
4. Calico/Cilium BGP peering:
Vstup na jednom interface, vystup na inom
Podla BGP route selection
Diagnostika rp_filter zahadzovani
Skontroluj Martian Packet logy
# Zapni martian logovanie
echo 1 > /proc/sys/net/ipv4/conf/all/log_martians
echo 1 > /proc/sys/net/ipv4/conf/eth0/log_martians
# Sleduj kernel logy
dmesg -w | grep -i martian
# IPv4: martian source 10.0.50.5 from 192.168.1.100, on dev eth0
# Alebo skontroluj dodatocne
dmesg | grep -i "martian\|ll header"
Skontroluj rp_filter statistiky
# IP statistiky obsahuju rp_filter zahodenia
cat /proc/net/snmp | grep -i ip
# Hladaj InAddrErrors - obsahuje rp_filter zahodenia
# Lepsie: Pouzi nstat pre delty
nstat -z | grep -i InAddrErrors
# IpInAddrErrors 543 0.0
# Sleduj v realnom case
watch -n 1 'nstat -z | grep InAddrErrors'
# Posli testovaci traffic, sleduj inkrement citaca → rp_filter zahadzuje
Over asymetriu routov
# Skontroluj ktory interface by SME pouzili na dosiahnutie zdroja
ip route get 10.0.50.5
# 10.0.50.5 via 10.100.0.1 dev eth1 src 10.100.0.100
# Ale paket prisiel na eth0!
# Asymetricke routovanie potvrdene → rp_filter obet
Oprava
Moznost 1: Volny mod (Odporucane)
# Nastav rp_filter na volny mod (2)
# Paket je akceptovany ak je zdroj dostupny cez AKYKOLVEK interface
# Docasne (do rebootu)
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 2 > /proc/sys/net/ipv4/conf/eth0/rp_filter
# Permanentne cez sysctl.conf
cat >> /etc/sysctl.d/99-rp-filter.conf << 'EOF'
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.eth0.rp_filter = 2
EOF
sysctl -p /etc/sysctl.d/99-rp-filter.conf
Moznost 2: Vypni pre konkretny interface
# Ak len jeden interface ma asymetricke routovanie
echo 0 > /proc/sys/net/ipv4/conf/eth1/rp_filter
# Poznamka: Efektivna hodnota je MAX(all, interface)
# Takze mozno budes musiet nastavit 'all' na 0 alebo 2 tiez
Moznost 3: Oprav routovanie (symetricke)
# Ak je to mozne, urob routovanie symetricke
# Pridaj policy-based routing aby odpovede isli spat
# tou istou cestou ako prisli requesty
# Oznac pakety prichadzajuce na eth1
iptables -t mangle -A PREROUTING -i eth1 -j MARK --set-mark 100
# Vytvor separatnu routovaciu tabulku
echo "100 vpn" >> /etc/iproute2/rt_tables
# Pridaj routy do tej tabulky
ip route add default via 10.100.0.1 dev eth1 table vpn
# Pouzi oznacene pakety s vpn tabulkou
ip rule add fwmark 100 table vpn
Kubernetes-specificky: DaemonSet pre Node konfig
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: rp-filter-config
namespace: kube-system
spec:
selector:
matchLabels:
app: rp-filter-config
template:
metadata:
labels:
app: rp-filter-config
spec:
hostNetwork: true
hostPID: true
initContainers:
- name: set-rp-filter
image: busybox:1.36
command:
- sh
- -c
- |
echo "Nastavujem rp_filter na volny mod..."
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 2 > /proc/sys/net/ipv4/conf/default/rp_filter
for iface in /proc/sys/net/ipv4/conf/*/rp_filter; do
echo 2 > "$iface" 2>/dev/null || true
done
echo "Hotovo. Aktualne nastavenia:"
cat /proc/sys/net/ipv4/conf/*/rp_filter
securityContext:
privileged: true
containers:
- name: pause
image: gcr.io/google_containers/pause:3.9
tolerations:
- operator: Exists
Reprodukcia v Labe
Pouzitie Docker-Compose
# docker-compose.yml
version: '3.8'
services:
server:
image: nginx:alpine
container_name: server
networks:
primary:
ipv4_address: 192.168.100.10
secondary:
ipv4_address: 10.200.0.10
sysctls:
- net.ipv4.conf.all.rp_filter=1 # Striktny mod
client:
image: alpine:3.18
container_name: client
command: sleep infinity
networks:
primary:
ipv4_address: 192.168.100.20
depends_on:
- server
- router
router:
image: alpine:3.18
container_name: router
command: sh -c "apk add iptables && sysctl -w net.ipv4.ip_forward=1 && sleep infinity"
cap_add:
- NET_ADMIN
networks:
primary:
ipv4_address: 192.168.100.1
secondary:
ipv4_address: 10.200.0.1
networks:
primary:
driver: bridge
ipam:
config:
- subnet: 192.168.100.0/24
secondary:
driver: bridge
ipam:
config:
- subnet: 10.200.0.0/24
Reprodukuj problem
docker compose up -d
# Nakonfiguruj asymetricke routovanie
docker exec server ip route add 192.168.100.20 via 10.200.0.1
docker exec client ip route add 10.200.0.10 via 192.168.100.1
# Testuj z klienta na sekundarnu IP servera
docker exec client sh -c "apk add curl && curl -v --connect-timeout 5 http://10.200.0.10"
# Timeout! Ale...
# tcpdump na serveri ukazuje pakety
docker exec server sh -c "apk add tcpdump && tcpdump -i eth1 -n" &
# SYN pakety viditelne!
# Skontroluj martian logy
docker exec server sh -c "echo 1 > /proc/sys/net/ipv4/conf/all/log_martians"
docker exec server dmesg | tail
# "martian source" spravy
# Oprav to
docker exec server sh -c "echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter"
# Skus znova
docker exec client curl -v http://10.200.0.10
# Funguje!
Monitoring
Prometheus Alerty
groups:
- name: rp-filter-drops
rules:
- alert: MartianPacketsDetected
expr: |
rate(node_netstat_Ip_InAddrErrors[5m]) > 0
for: 5m
labels:
severity: warning
annotations:
summary: "Mozne rp_filter zahodenia na {{ $labels.instance }}"
description: "InAddrErrors stupa - skontroluj asymetricke routovanie"
- alert: HighMartianPacketRate
expr: |
rate(node_netstat_Ip_InAddrErrors[5m]) > 100
for: 2m
labels:
severity: critical
annotations:
summary: "Vysoka miera zahodenych paketov na {{ $labels.instance }}"
Checklist
## rp_filter Debugging Checklist
### Detekcia
- [ ] tcpdump ukazuje pakety ktore aplikacia nevidi
- [ ] conntrack/iptables neukazuje zaznamy pre flow
- [ ] Zapni log_martians a skontroluj dmesg
- [ ] Skontroluj InAddrErrors cez nstat
### Analyza root cause
- [ ] Over asymetriu routov: `ip route get <source_ip>`
- [ ] Porovnaj ingress interface vs route output interface
- [ ] Skontroluj aktualne rp_filter nastavenia
### Moznosti opravy
- [ ] Nastav rp_filter=2 (volny mod) - odporucane
- [ ] Oprav routovanie na symetricke
- [ ] Vypni rp_filter=0 ak bezpecnost dovoli
### Prevencia
- [ ] Dokumentuj sietovu topologiu
- [ ] Pridaj rp_filter konfiguraciu do provisioningu nodov
- [ ] Monitoruj InAddrErrors metriku
- [ ] Testuj konektivitu pri sietovych zmenach
Zaver
rp_filter pasca je jeden z tych problemov co vas nuti pochybovat o vasich debugging schopnostiach. Vidite pakety v tcpdump. Firewall ich neblokuje. Aplikacia pocuva na spravnom porte. Ale spojenie timeoutuje. Pakety su zahadzovane na mieste kde vacsina debugovacich nastrojov neukazuje - medzi sietovym interface a iptables/conntrack spracovanim.
Klucovy pohlad je pochopit ze Linux reverse path filtering je bezpecnostna funkcia ktora nastava velmi skoro v spracovani paketov, pred vacsinou debugovacich hookov. Ked pakety prichadzaju na jeden interface ale kernelova routa k zdrojovej adrese ukazuje na iny interface, striktny mod (rp_filter=1) paket zahodi ako potencialne spoofovany.
Oprava je zvycajne jednoducha: prepni na volny mod (rp_filter=2), ktory vyzaduje len aby zdrojova adresa bola routovatelna cez nejaky interface, nie nutne ten isty na ktory paket prisiel.
Klucove principy:
- tcpdump zachytava pred rp_filter - vidiet pakety v tcpdump neznamena ze dosiahnu aplikaciu
- conntrack/iptables nevidi zahodenia - tie nastanu pred connection trackingom
- Kontroluj martian logy - zapni log_martians a sleduj dmesg
- rp_filter=2 je zvycajne bezpecny - stale poskytuje anti-spoofing ochranu
- Multi-homed = riziko asymetrickeho routovania - VPN, dual NIC, BGP peering vytvaraju asymetricke cesty
Suvisiace clanky
- PMTU Blackholes v Overlay Networks - Dalsi scenar neviditelneho zahadzovanie paketov
- Kubernetes conntrack Table Exhaustion - Connection tracking edge cases
Súvisiace články
Kubernetes conntrack Vyčerpanie: Tichý Zabijak Paketov
Náhodné DNS timeouty, dropped spojenia, služby timeout-ujú. Vaša nf_conntrack tabuľka je plná. Ukážem ako diagnostikovať, monitorovať a opraviť tento K8s networking problém.
Kubernetes Ghost Connections: Zastarané Conntrack DNAT Záznamy
Service vracia zlé pod IP po škálovaní. Príčina: Linux conntrack drží DNAT záznamy dlhšie ako existujú pody, smeruje traffic na zmazané endpointy.
Vyčerpanie Ephemeral Portov: Node Ktorý 'Pokazí'
Jeden Kubernetes node začne zlyhávať pripojenia k externým službám zatiaľ čo pody vyzerajú zdravé. Skrytá príčina: sidecar proxy vyčerpávajú ephemeral porty krátkodobými spojeniami.
'No space left on device' s 40% voľného disku: Inode a OverlayFS Death Spiral
df -h ukazuje 40% voľného miesta. Ale váš kontajner stále padá s ENOSPC. Vinník: vyčerpanie inodov na overlayfs vrstvách, neviditeľné pre štandardný monitoring.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.