Files
hpnn-proto/docs/ROUTING_DATAFLOW.md
T

12 KiB
Raw Blame History

Потоки данных подсистемы L3-маршрутизации

Дата: 2026-08-17 Область: транзитная маршрутизация узла hpnn-lb — всё, что проходит через таблицы 20 (LPM) и 21 (adjacency). Балансировка (таблицы 10–12) показана только как точка ответвления и подробно описана в STEP2_SUMMARY.md. Источник: lb/pipeline.sh, lb/topology.env (состояние на 4 члена пула).

Ключевое свойство: сетевой стек ядра контейнера в транзите не участвует. В ядре живут только два адреса — 10.20.0.253 и 10.30.0.253 на internal-портах для health-проб. Все решения о пересылке принимает OpenFlow.

1. Топология портов моста br-lb

flowchart LR
    CL["Клиент<br/>192.168.5.0/24"]

    subgraph BR["br-lb — OpenFlow datapath"]
        direction TB
        PUB["pub0 — ofport 1<br/>macvlan @ enp3s0<br/>02:42:c0:a8:05:14<br/><i>шлюз 192.168.5.20, VIP .21</i>"]
        P2["p2 — ofport 2<br/>02:42:0a:14:00:01<br/><i>шлюз 10.20.0.1</i>"]
        P3["p3 — ofport 3<br/>02:42:0a:1e:00:01<br/><i>шлюз 10.30.0.1</i>"]
        HC2["hcif-p2 — ofport 4<br/>10.20.0.253 <b>в ядре</b>"]
        HC3["hcif-p3 — ofport 5<br/>10.30.0.253 <b>в ядре</b>"]
    end

    BE1["be1 10.20.0.2"]
    BE3["be3 10.20.0.3"]
    BE2["be2 10.30.0.2"]
    BE4["be4 10.30.0.3"]
    HCD["hcd — демон<br/>health-check"]

    CL <--> PUB
    P2 <--> BE1
    P2 <--> BE3
    P3 <--> BE2
    P3 <--> BE4
    HCD <--> HC2
    HCD <--> HC3

2. Конвейер таблиц: путь маршрутизируемого пакета

Красным помечены точки отбрасывания, пунктиром — ответвление в балансировку.

flowchart TD
    IN(["Пакет на порту моста"]) --> T0

    T0{"<b>Таблица 0</b><br/>классификация по in_port<br/>и типу трафика"}

    T0 -->|"arp"| T5["<b>5</b> ARP-респондер<br/>для .20/.21/10.20.0.1/<br/>10.30.0.1/.253"]
    T0 -->|"icmp echo<br/>на адреса узла"| T6["<b>6</b> ICMP echo-респондер"]
    T0 -->|"in_port=1 (pub)<br/>+ dst ∈ {VIP, узел,<br/>10.20/24, 10.30/24}"| LEARN["<b>learn</b> → запись<br/>adjacency клиента<br/>в таблицу 21<br/>(priority 90, 300 c)"]
    T0 -->|"in_port=2,3 (priv)"| T15["<b>15</b> обратный путь"]
    T0 -->|"in_port=4,5 (hcif)"| T20
    T0 -->|"иначе"| DROP0(["drop"])

    LEARN --> T10{"<b>Таблица 10</b><br/>листенеры"}
    T10 -.->|"tcp dst=VIP:80"| LB["<b>11/12</b> слот → член пула,<br/>DNAT ct(commit,nat)"]
    T10 -->|"прочий IP<br/>(прямое обращение<br/>к бэкенду)"| T20
    T10 -->|"IP на VIP<br/>без листенера"| DROPV(["drop"])
    LB --> T20

    T15 -->|"tcp"| CT["ct(zone=1, nat)<br/>снятие DNAT,<br/>если сессия известна"] --> T16["<b>16</b> пост-ct"] --> T20
    T15 -->|"прочий IP"| T20

    T20{"<b>Таблица 20</b> — LPM<br/>приоритет = длина префикса"}
    T20 -->|"/32 10.20.0.253<br/>/32 10.30.0.253<br/><i>адрес узла, TTL не трогаем</i>"| T21
    T20 -->|"/24 10.20.0.0<br/>dec_ttl, src MAC = p2"| T21
    T20 -->|"/24 10.30.0.0<br/>dec_ttl, src MAC = p3"| T21
    T20 -->|"/24 192.168.5.0<br/>dec_ttl, src MAC = pub0"| T21
    T20 -->|"нет маршрута<br/><i>default отсутствует</i>"| DROP20(["drop"])

    T21{"<b>Таблица 21</b> — adjacency<br/>next-hop MAC + порт"}
    T21 -->|"prio 100: be1..be4,<br/>hcif — статически"| OUT(["output: 2 / 3 / 4 / 5"])
    T21 -->|"prio 90: клиент,<br/>из действия learn"| OUTC(["output: 1"])
    T21 -->|"MAC неизвестен"| DROP21(["drop"])

    classDef d fill:#7f1d1d,stroke:#ef4444,color:#fff
    class DROP0,DROPV,DROP20,DROP21 d

Почему нумерация именно такая: goto_table в OpenFlow разрешает переход только вперёд, поэтому обратный путь (15/16) обязан стоять до общей маршрутизации (20), а не после неё.

3. Четыре потока маршрутизации

R1. Клиент → бэкенд напрямую (публичный → приватный сегмент)

Балансировка не участвует: клиент обращается на 10.20.0.2:8080, имея маршрут через 192.168.5.20.

sequenceDiagram
    autonumber
    participant C as Клиент 192.168.5.13
    participant P as pub0 (1)
    participant OF as Таблицы OpenFlow
    participant Q as p2 (2)
    participant B as be1 10.20.0.2

    C->>P: ARP who-has 192.168.5.20
    P->>OF: таблица 5 — ARP-респондер
    OF-->>C: is-at 02:42:c0:a8:05:14
    C->>P: IP src=192.168.5.13 dst=10.20.0.2, TTL=64
    P->>OF: t0 learn (adj клиента в t21) → t10
    Note over OF: t10: не VIP → goto 20
    Note over OF: t20: LPM /24 10.20.0.0<br/>dec_ttl → 63, src MAC = p2
    Note over OF: t21: dst MAC = 02:42:0a:14:00:02, порт 2
    OF->>Q: пересылка
    Q->>B: пакет, src остался клиентским
    B->>Q: ответ src=10.20.0.2 dst=192.168.5.13
    Q->>OF: t0 → t15 → ct(nat) сессии нет → t16 → t20
    Note over OF: t20: LPM /24 192.168.5.0<br/>dec_ttl, src MAC = pub0
    Note over OF: t21: prio 90 — запись, созданная learn
    OF->>P: порт 1
    P->>C: ответ

R2. Транзит между приватными сегментами (be1 → be2)

Единственный поток, где узел работает как чистый маршрутизатор в обе стороны.

flowchart LR
    BE1["be1<br/>10.20.0.2"] -->|"dst=10.30.0.2:8080<br/>TTL 64"| A["p2 (2)"]
    A --> B["t0: in_port=2 → t15"]
    B --> C["t15: tcp → ct(zone=1,nat)<br/>сессии в таблице нет —<br/>пакет не меняется"]
    C --> D["t16 → t20"]
    D --> E["t20: /24 10.30.0.0<br/>dec_ttl → 63<br/>src MAC = 02:42:0a:1e:00:01"]
    E --> F["t21: dst MAC =<br/>02:42:0a:1e:00:02<br/>output:3"]
    F --> G["p3 (3)"] --> BE2["be2<br/>10.30.0.2<br/><i>client=10.20.0.2</i>"]

Обратный путь be2 → be1 симметричен: t0 → t15 → t16 → t20 (/24 10.20.0.0) → t21 → p2. Никакой трансляции ни в одном направлении.

R3. Обратный трафик балансируемой сессии (бэкенд → клиент)

Тот же путь, что и R1-ответ, но с обязательным проходом через ct: именно там src возвращается с адреса бэкенда на VIP.

flowchart LR
    BE["be1 10.20.0.2:8080"] -->|"src=10.20.0.2<br/>dst=192.168.5.13"| A["p2 (2)"]
    A --> B["t0: in_port=2 → t15"]
    B --> C["t15: ct(zone=1, nat)<br/><b>un-DNAT</b>:<br/>src → 192.168.5.21:80"]
    C --> D["t16 → t20"]
    D --> E["t20: /24 192.168.5.0<br/>dec_ttl, src MAC = pub0"]
    E --> F["t21 prio 90:<br/>MAC клиента из learn<br/>output:1"]
    F --> CL["Клиент<br/>видит ответ от VIP"]

Почему обратный трафик вообще приходит на узел: у бэкендов профиль A — маршрут на клиентский префикс 192.168.5.0/24 направлен на шлюз сегмента (10.20.0.1 / 10.30.0.1), а default остаётся на шлюзе Docker. Собственный исходящий трафик бэкенда через балансировщик не идёт.

R4. Health-пробы — единственный трафик, порождённый узлом

sequenceDiagram
    autonumber
    participant H as hcd (ядро контейнера)
    participant I as hcif-p2 (4)
    participant OF as OpenFlow
    participant Q as p2 (2)
    participant B as be1

    Note over H: Dialer.LocalAddr = 10.20.0.253
    H->>I: SYN 10.20.0.253 → 10.20.0.2:8080
    I->>OF: t0: in_port=4 → <b>сразу t20</b><br/>без learn и без ct
    Note over OF: t20: /24 10.20.0.0, t21: output:2
    OF->>Q: →
    Q->>B: проба GET /healthz
    B->>Q: SYN-ACK 10.20.0.2 → 10.20.0.253
    Q->>OF: t0 → t15 → t16 → t20
    Note over OF: t20: <b>/32 10.20.0.253</b> prio 32<br/>TTL не уменьшается — пакет<br/>адресован самому узлу
    Note over OF: t21: output:4
    OF->>I: →
    I->>H: ответ, член up

Пробы идут с .253, а не с VIP, намеренно: иначе ответ бэкенда попал бы в логику обратной трансляции (t15) и до пробера не дошёл.

4. Таблица решений

Таблица Матч Действие Комментарий
0 in_port=1, dst ∈ {VIP, .20, 10.20/24, 10.30/24} learn → t10 обучение сужено, чтобы в t21 не попадал broadcast-шум LAN
0 in_port=1, прочий IP drop
0 in_port=2,3 → t15 обратный путь и транзит
0 in_port=4,5 → t20 трафик стека узла, минуя ct и learn
10 ip, не VIP → t20 точка входа маршрутизации из публичного сегмента
15 tcp ct(zone=1, nat) → t16 un-DNAT для балансируемых сессий, no-op для транзита
20 /32 адреса .253 prio 32, → t21 TTL не уменьшается
20 /24 три префикса prio 24, dec_ttl + mod_dl_src LPM через приоритет
20 остальное prio 0, drop дефолтного маршрута нет
21 nw_dst = be1..be4, hcif prio 100, mod_dl_dst + output статические MAC из docker-compose.yml
21 nw_dst = клиент prio 90, из learn hard_timeout 300 с

5. Где поток обрывается

Точка Причина Как увидеть
t0 prio 0 не-IP и не-ARP: IPv6, DHCP, broadcast lbctl flows 0
t0 prio 100 (pub) IP из публичного сегмента не стенду lbctl flows 0
t10 prio 150 IP на VIP без листенера lbctl flows 10
t20 prio 0 назначение без маршрута lbctl flows 20 | grep priority=0
t21 prio 0 next-hop MAC неизвестен (запись learn истекла) lbctl flows 21

6. Чего в этих потоках нет

  • ICMP-ошибок узел не генерирует — ни TTL exceeded, ни fragmentation needed. Пакет с TTL=1 просто исчезает, traceroute через стенд узел не покажет, PMTUD не работает.
  • Фрагменты IP не обрабатываются: правила матчат L4-заголовок, которого во втором и последующих фрагментах нет.
  • ARP-резолвера next-hop нет. MAC бэкендов статичны, MAC клиентов узнаются действием learn — то есть только для тех, кто первым обратился к стенду.
  • Дефолтного маршрута нет по построению: узел маршрутизирует только три известных префикса.
  • Только IPv4.