202 lines
14 KiB
Bash
202 lines
14 KiB
Bash
#!/bin/bash
|
|||
|
|
# Генератор OpenFlow-пайплайна br-lb. Печатает flow-файл в stdout.
|
||
|
|
# Применяется атомарным бандлом: ovs-ofctl --bundle -O OpenFlow15 replace-flows.
|
||
|
|
#
|
||
|
|
# Карта таблиц:
|
||
|
|
# 0 — классификация по in_port и типу трафика, обучение MAC клиентов
|
||
|
|
# 5 — ARP-респондер для собственных адресов узла и VIP
|
||
|
|
# 6 — ICMP echo-респондер для тех же адресов
|
||
|
|
# 10 — листенеры: хэш сессии в слот (multipath), прочий IP -> маршрутизация
|
||
|
|
# 11 — таблица слотов: слот -> член пула. Заливает и обновляет демон hcd
|
||
|
|
# 12 — применение DNAT выбранным членом пула (по reg2)
|
||
|
|
# 15 — обратный путь из приватных сегментов (снятие DNAT через ct)
|
||
|
|
# 16 — пост-ct hook (счётчики, место под будущие проверки)
|
||
|
|
# 20 — маршрутизация (LPM через приоритет = длина префикса)
|
||
|
|
# 21 — adjacency: next-hop MAC + выходной порт
|
||
|
|
#
|
||
|
|
# Регистры: reg1 — номер слота, reg2 — идентификатор члена пула.
|
||
|
|
#
|
||
|
|
# Нумерация не произвольна: goto_table в OpenFlow разрешает переход только
|
||
|
|
# вперёд, поэтому обратный путь (15/16) стоит до общей маршрутизации (20).
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
# shellcheck disable=SC1091
|
||
|
|
source "$(dirname "$0")/topology.env"
|
||
|
|
|
||
|
|
ip2hex() { local IFS=.; read -ra o <<<"$1"; printf '0x%02x%02x%02x%02x' "${o[0]}" "${o[1]}" "${o[2]}" "${o[3]}"; }
|
||
|
|
mac2hex() { echo "0x${1//:/}"; }
|
||
|
|
|
||
|
|
PUB_MAC_H=$(mac2hex "$PUB_MAC")
|
||
|
|
P2_MAC_H=$(mac2hex "$P2_MAC")
|
||
|
|
P3_MAC_H=$(mac2hex "$P3_MAC")
|
||
|
|
|
||
|
|
LB_PUB_IP_H=$(ip2hex "$LB_PUB_IP")
|
||
|
|
VIP_H=$(ip2hex "$VIP")
|
||
|
|
LB_P2_IP_H=$(ip2hex "$LB_P2_IP")
|
||
|
|
LB_P3_IP_H=$(ip2hex "$LB_P3_IP")
|
||
|
|
|
||
|
|
HC2_MAC_H=$(mac2hex "$HC2_MAC")
|
||
|
|
HC3_MAC_H=$(mac2hex "$HC3_MAC")
|
||
|
|
HC2_IP_H=$(ip2hex "$HC2_IP")
|
||
|
|
HC3_IP_H=$(ip2hex "$HC3_IP")
|
||
|
|
|
||
|
|
# Действие обучения: по IP-адресу отправителя создаёт в таблице 21 запись
|
||
|
|
# adjacency для обратного пути — куда и с каким MAC отправлять ответы этому
|
||
|
|
# клиенту. Заменяет ARP-резолвер, которого у чисто-OpenFlow узла нет.
|
||
|
|
LEARN="learn(table=21,priority=90,hard_timeout=300,eth_type=0x0800,NXM_OF_IP_DST[]=NXM_OF_IP_SRC[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],load:$PUB_MAC_H->NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[])"
|
||
|
|
|
||
|
|
# arp_responder <ip> <ip_hex> <mac> <mac_hex>
|
||
|
|
arp_responder() {
|
||
|
|
echo "table=5,priority=100,arp,arp_op=1,arp_tpa=$1 actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],mod_dl_src:$3,load:0x2->NXM_OF_ARP_OP[],move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],load:$4->NXM_NX_ARP_SHA[],load:$2->NXM_OF_ARP_SPA[],IN_PORT"
|
||
|
|
}
|
||
|
|
|
||
|
|
# icmp_responder <ip> <ip_hex> <mac_hex>
|
||
|
|
icmp_responder() {
|
||
|
|
echo "table=6,priority=100,icmp,nw_dst=$1,icmp_type=8 actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],load:$3->NXM_OF_ETH_SRC[],move:NXM_OF_IP_SRC[]->NXM_OF_IP_DST[],load:$2->NXM_OF_IP_SRC[],load:0->NXM_OF_ICMP_TYPE[],IN_PORT"
|
||
|
|
}
|
||
|
|
|
||
|
|
cat <<EOF
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 0 — классификация
|
||
|
|
# =============================================================================
|
||
|
|
# ARP обрабатывает собственный респондер (таблица 5): ядро в форвардинге не
|
||
|
|
# участвует, поэтому штатного ARP-стека у узла нет.
|
||
|
|
table=0,priority=200,arp actions=goto_table:5
|
||
|
|
|
||
|
|
# ICMP echo-request на собственные адреса узла и на VIP — в респондер.
|
||
|
|
table=0,priority=190,icmp,nw_dst=$LB_PUB_IP,icmp_type=8 actions=goto_table:6
|
||
|
|
table=0,priority=190,icmp,nw_dst=$VIP,icmp_type=8 actions=goto_table:6
|
||
|
|
table=0,priority=190,icmp,nw_dst=$LB_P2_IP,icmp_type=8 actions=goto_table:6
|
||
|
|
table=0,priority=190,icmp,nw_dst=$LB_P3_IP,icmp_type=8 actions=goto_table:6
|
||
|
|
|
||
|
|
# Вход из публичного сегмента: запоминаем MAC отправителя как adjacency для
|
||
|
|
# обратного пути (замена ARP-резолвера на клиентской стороне), затем к
|
||
|
|
# листенерам. Записи живут 300 с и обновляются каждым пакетом клиента.
|
||
|
|
#
|
||
|
|
# Обучение включено только для трафика, адресованного стенду (VIP, адрес узла,
|
||
|
|
# приватные сегменты). Иначе в таблицу 21 попадал бы весь широковещательный
|
||
|
|
# шум LAN — macvlan-порт видит DHCP, mDNS и SSDP всех соседей по сегменту.
|
||
|
|
table=0,priority=110,in_port=$PUB_OFPORT,ip,nw_dst=$VIP actions=$LEARN,goto_table:10
|
||
|
|
table=0,priority=110,in_port=$PUB_OFPORT,ip,nw_dst=$LB_PUB_IP actions=$LEARN,goto_table:10
|
||
|
|
table=0,priority=110,in_port=$PUB_OFPORT,ip,nw_dst=$P2_NET actions=$LEARN,goto_table:10
|
||
|
|
table=0,priority=110,in_port=$PUB_OFPORT,ip,nw_dst=$P3_NET actions=$LEARN,goto_table:10
|
||
|
|
table=0,priority=100,in_port=$PUB_OFPORT,ip actions=drop
|
||
|
|
|
||
|
|
# Вход из приватных сегментов: обратный путь балансируемых сессий и транзит.
|
||
|
|
table=0,priority=100,in_port=$P2_OFPORT,ip actions=goto_table:15
|
||
|
|
table=0,priority=100,in_port=$P3_OFPORT,ip actions=goto_table:15
|
||
|
|
|
||
|
|
# Health-пробы, порождённые самим узлом: сразу в маршрутизацию, без ct и без
|
||
|
|
# обучения — это трафик из стека узла, а не клиентская сессия.
|
||
|
|
table=0,priority=100,in_port=$HC2_OFPORT,ip actions=goto_table:20
|
||
|
|
table=0,priority=100,in_port=$HC3_OFPORT,ip actions=goto_table:20
|
||
|
|
|
||
|
|
# Всё остальное (не-IP, не-ARP: broadcast, IPv6, DHCP) в прототипе не нужно.
|
||
|
|
table=0,priority=0 actions=drop
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 5 — ARP-респондер
|
||
|
|
# =============================================================================
|
||
|
|
$(arp_responder "$LB_PUB_IP" "$LB_PUB_IP_H" "$PUB_MAC" "$PUB_MAC_H")
|
||
|
|
$(arp_responder "$VIP" "$VIP_H" "$PUB_MAC" "$PUB_MAC_H")
|
||
|
|
$(arp_responder "$LB_P2_IP" "$LB_P2_IP_H" "$P2_MAC" "$P2_MAC_H")
|
||
|
|
$(arp_responder "$LB_P3_IP" "$LB_P3_IP_H" "$P3_MAC" "$P3_MAC_H")
|
||
|
|
# Адреса источника health-проб: бэкенд резолвит их, отвечая на пробу.
|
||
|
|
$(arp_responder "$HC2_IP" "$HC2_IP_H" "$HC2_MAC" "$HC2_MAC_H")
|
||
|
|
$(arp_responder "$HC3_IP" "$HC3_IP_H" "$HC3_MAC" "$HC3_MAC_H")
|
||
|
|
table=5,priority=0 actions=drop
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 6 — ICMP echo-респондер
|
||
|
|
# =============================================================================
|
||
|
|
$(icmp_responder "$LB_PUB_IP" "$LB_PUB_IP_H" "$PUB_MAC_H")
|
||
|
|
$(icmp_responder "$VIP" "$VIP_H" "$PUB_MAC_H")
|
||
|
|
$(icmp_responder "$LB_P2_IP" "$LB_P2_IP_H" "$P2_MAC_H")
|
||
|
|
$(icmp_responder "$LB_P3_IP" "$LB_P3_IP_H" "$P3_MAC_H")
|
||
|
|
table=6,priority=0 actions=drop
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 10 — листенеры
|
||
|
|
# =============================================================================
|
||
|
|
# Единственный листенер прототипа: TCP $VIP:$VIP_PORT.
|
||
|
|
# multipath раскладывает сессию в один из $SLOTS слотов по симметричному
|
||
|
|
# L4-хэшу; basis = POOL_ID, поэтому разные пулы дают независимые раскладки.
|
||
|
|
# Хэш симметричен, то есть прямое и обратное направление дают один слот —
|
||
|
|
# свойство, необходимое для будущего stateless-датапаса.
|
||
|
|
table=10,priority=200,tcp,nw_dst=$VIP,tp_dst=$VIP_PORT actions=multipath(symmetric_l4,$POOL_ID,modulo_n,$SLOTS,0,NXM_NX_REG1[]),goto_table:11
|
||
|
|
|
||
|
|
# Трафик на VIP, для которого листенера нет, — отбрасываем со счётчиком.
|
||
|
|
table=10,priority=150,ip,nw_dst=$VIP actions=drop
|
||
|
|
|
||
|
|
# Прочий IP из публичного сегмента маршрутизируется обычным образом
|
||
|
|
# (клиент может обратиться напрямую к бэкенду, минуя балансировку).
|
||
|
|
table=10,priority=100,ip actions=goto_table:20
|
||
|
|
table=10,priority=0 actions=drop
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 11 — таблица слотов
|
||
|
|
# =============================================================================
|
||
|
|
# Содержимое ($SLOTS правил вида reg1=<слот> -> reg2=<член пула>) рассчитывает
|
||
|
|
# и заливает демон hcd по Maglev-раскладке живых членов пула. Здесь только
|
||
|
|
# основание fail-close: пока пул пуст, трафик на VIP отбрасывается со
|
||
|
|
# счётчиком, а не уходит на заведомо мёртвый бэкенд.
|
||
|
|
#
|
||
|
|
# Счётчики правил этой таблицы дают бесплатную per-member статистику.
|
||
|
|
table=11,priority=0 actions=drop
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 12 — DNAT выбранным членом пула
|
||
|
|
# =============================================================================
|
||
|
|
# Слот положил идентификатор члена в reg2. SNAT не выполняется: бэкенд видит
|
||
|
|
# реальный адрес клиента.
|
||
|
|
table=12,priority=100,ip,reg2=0x1 actions=ct(commit,zone=$CT_ZONE,nat(dst=$BE1_IP:$BE1_PORT),table=20)
|
||
|
|
table=12,priority=100,ip,reg2=0x2 actions=ct(commit,zone=$CT_ZONE,nat(dst=$BE2_IP:$BE2_PORT),table=20)
|
||
|
|
table=12,priority=100,ip,reg2=0x3 actions=ct(commit,zone=$CT_ZONE,nat(dst=$BE3_IP:$BE3_PORT),table=20)
|
||
|
|
table=12,priority=100,ip,reg2=0x4 actions=ct(commit,zone=$CT_ZONE,nat(dst=$BE4_IP:$BE4_PORT),table=20)
|
||
|
|
table=12,priority=0 actions=drop
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 15 — обратный путь из приватных сегментов
|
||
|
|
# =============================================================================
|
||
|
|
# ct(nat) без commit снимает ранее выполненный DNAT: source возвращается к VIP.
|
||
|
|
# Для сессий, которых нет в таблице соединений (трафик be1<->be2, обращения
|
||
|
|
# бэкендов к клиентским префиксам), пакет проходит без изменений.
|
||
|
|
table=15,priority=100,tcp actions=ct(table=16,zone=$CT_ZONE,nat)
|
||
|
|
table=15,priority=50,ip actions=goto_table:20
|
||
|
|
table=15,priority=0 actions=drop
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 16 — после ct
|
||
|
|
# =============================================================================
|
||
|
|
table=16,priority=100,ip actions=goto_table:20
|
||
|
|
table=16,priority=0 actions=drop
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 20 — маршрутизация
|
||
|
|
# =============================================================================
|
||
|
|
# Приоритет = длина префикса, что даёт longest-prefix match средствами
|
||
|
|
# OpenFlow. Дефолтного маршрута нет: неизвестное назначение отбрасывается.
|
||
|
|
#
|
||
|
|
# Собственные адреса узла (/32, приоритет 32) — ответы бэкендов на health-пробы.
|
||
|
|
# TTL для них не уменьшается: пакет адресован самому узлу, а не транзитный.
|
||
|
|
table=20,priority=32,ip,nw_dst=$HC2_IP actions=goto_table:21
|
||
|
|
table=20,priority=32,ip,nw_dst=$HC3_IP actions=goto_table:21
|
||
|
|
table=20,priority=24,ip,nw_dst=$P2_NET actions=dec_ttl,mod_dl_src:$P2_MAC,goto_table:21
|
||
|
|
table=20,priority=24,ip,nw_dst=$P3_NET actions=dec_ttl,mod_dl_src:$P3_MAC,goto_table:21
|
||
|
|
table=20,priority=24,ip,nw_dst=$PUB_NET actions=dec_ttl,mod_dl_src:$PUB_MAC,goto_table:21
|
||
|
|
table=20,priority=0 actions=drop
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Таблица 21 — adjacency (next-hop MAC + выходной порт)
|
||
|
|
# =============================================================================
|
||
|
|
# Бэкенды прописаны статически: их MAC фиксирован в docker-compose.yml.
|
||
|
|
table=21,priority=100,ip,nw_dst=$BE1_IP actions=mod_dl_dst:$BE1_MAC,output:$P2_OFPORT
|
||
|
|
table=21,priority=100,ip,nw_dst=$BE2_IP actions=mod_dl_dst:$BE2_MAC,output:$P3_OFPORT
|
||
|
|
table=21,priority=100,ip,nw_dst=$BE3_IP actions=mod_dl_dst:$BE3_MAC,output:$P2_OFPORT
|
||
|
|
table=21,priority=100,ip,nw_dst=$BE4_IP actions=mod_dl_dst:$BE4_MAC,output:$P3_OFPORT
|
||
|
|
# Ответы на health-пробы — в стек узла через internal-порты.
|
||
|
|
table=21,priority=100,ip,nw_dst=$HC2_IP actions=mod_dl_dst:$HC2_MAC,output:$HC2_OFPORT
|
||
|
|
table=21,priority=100,ip,nw_dst=$HC3_IP actions=mod_dl_dst:$HC3_MAC,output:$HC3_OFPORT
|
||
|
|
# priority=90 — записи клиентов, устанавливаемые действием learn из таблицы 0.
|
||
|
|
table=21,priority=0 actions=drop
|
||
|
|
EOF
|