Späť na blog

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:

  1. tcpdump zachytava pred rp_filter - vidiet pakety v tcpdump neznamena ze dosiahnu aplikaciu
  2. conntrack/iptables nevidi zahodenia - tie nastanu pred connection trackingom
  3. Kontroluj martian logy - zapni log_martians a sleduj dmesg
  4. rp_filter=2 je zvycajne bezpecny - stale poskytuje anti-spoofing ochranu
  5. Multi-homed = riziko asymetrickeho routovania - VPN, dual NIC, BGP peering vytvaraju asymetricke cesty

Suvisiace clanky

Súvisiace články

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "Pakety prichadzaju ale aplikacia timeoutuje: rp_filter pasca v Kubernetes". https://www.michal-drozd.com/sk/blog/linux-rp-filter-asymetricke-routovanie/ (Publikované 12. decembra 2025).