Files
ayurishchevandClaude Opus 5 fa389f119b Шаг 5: динамическое изучение MAC/ARP для бэкендов
MAC каждого члена пула больше не статическая константа в topology.env,
а резолвится демоном hcd через обычный ARP ядра — в реальном
окружении MAC бэкенда заранее не известен (сервер ещё не подключён,
NIC может замениться), топология не описывается статически, в отличие
от стенда.

hcif-порты (единственные адреса узла в ядре) уже были настоящими
L3-интерфейсами в тех же сегментах, что и бэкенды — единственное, что
мешало обычному ARP, это permanent-записи ip neigh в entrypoint.sh.
Убрав их и добавив hc/neigh.go (читает ip -json neigh show, точечно
заливает бандл в таблицу 21 на том же тикере, что и health-пробы),
получили резолвер без нового OpenFlow-контроллера.

Таблица 21 стала единственным источником MAC для обоих путей —
маршрутизируемого и коммутируемого (шаг 3): таблица 12 больше не
дублирует MAC инлайново, ct(commit) ведёт сразу в таблицу 21.

Исправлен попутно найденный баг: после apply.sh (replace-flows)
таблица 21 не восстанавливалась, поскольку syncNeighbors сравнивал
MAC с памятью демона, а не с датапасом. Добавлен force-режим по
аналогии с Agent.apply(), плюс регрессионная проверка в verify.sh.

Проверено на живом стенде: MAC всех четырёх членов резолвлен и
совпадает с реальными интерфейсами; смена MAC "железа" обнаружена и
применена без вмешательства за счёт штатного старения ARP ядра.
Регрессия: 94 из 94 проверок.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:33:48 +03:00

377 lines
24 KiB
Bash
Executable File

#!/bin/bash
# Генератор OpenFlow-пайплайна br-lb. Печатает flow-файл в stdout.
# Применяется атомарным бандлом: ovs-ofctl --bundle -O OpenFlow15 replace-flows.
#
# Карта таблиц:
# 0 — определение сегмента (reg0) по in_port, обучение MAC и adjacency
# 1 — классификация: ARP, ICMP, листенер, L3-трафик к шлюзу, коммутация
# 5 — ARP-респондер для собственных адресов узла и VIP, затем коммутация
# 6 — ICMP echo-респондер для тех же адресов
# 10 — листенеры: хэш сессии в слот (multipath), прочий IP -> маршрутизация
# 11 — таблица слотов: слот -> член пула. Заливает и обновляет демон hcd
# 12 — применение выбранного члена: DNAT + выдача (L2 либо маршрутизация)
# 15 — обратный путь L3 из приватных сегментов (снятие DNAT через ct)
# 16 — пост-ct hook (счётчики, место под будущие проверки)
# 20 — маршрутизация (LPM через приоритет = длина префикса)
# 21 — adjacency: next-hop MAC + выходной порт
# 24 — обратная трансляция коммутируемого трафика сегмента (шаг 3)
# 25 — L2-коммутация сегмента: FDB, broadcast и unknown flood (шаг 3)
#
# Регистры: reg0 — сегмент (1 — публичный, 2 и 3 — приватные), reg1 — номер
# слота, reg2 — идентификатор члена пула.
#
# Нумерация не произвольна: goto_table в OpenFlow разрешает переход только
# вперёд, поэтому обратный путь (15/16) стоит до общей маршрутизации (20), а
# коммутация сегмента (24/25) — после неё: в неё попадают и кадры, прошедшие
# DNAT в таблице 12.
#
# Ключевое отличие шага 3: приватный сегмент коммутирует сам br-lb, каждая ВМ
# подключена отдельным портом. Поэтому ответ бэкенда клиенту-соседу по подсети
# проходит через таблицу 24, где ct снимает DNAT, — при этом ни в ОС ВМ, ни в
# адресации сегмента ничего не меняется, а внутрисегментный трафик не
# блокируется.
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")
P2_VIP_H=$(ip2hex "$P2_VIP")
P3_VIP_H=$(ip2hex "$P3_VIP")
HC2_MAC_H=$(mac2hex "$HC2_MAC")
HC3_MAC_H=$(mac2hex "$HC3_MAC")
HC2_IP_H=$(ip2hex "$HC2_IP")
HC3_IP_H=$(ip2hex "$HC3_IP")
# --- описание сегментов ------------------------------------------------------
# Производные величины: номер сегмента в reg0, состав портов и члены пула.
P2_REG=0x2
P3_REG=0x3
P2_PORTS="$P2_OFPORT $HC2_OFPORT $BE1_OFPORT $BE3_OFPORT $CLI2_OFPORT"
P3_PORTS="$P3_OFPORT $HC3_OFPORT $BE2_OFPORT $BE4_OFPORT $CLI3_OFPORT"
P2_MEMBERS="BE1 BE3"
P3_MEMBERS="BE2 BE4"
# flood_ports <список ofport> — действие рассылки по сегменту. Порт входа OVS
# исключает сам, поэтому отдельного «кроме in_port» не требуется.
flood_ports() {
local out="" p
for p in $1; do out="$out,output:$p"; done
echo "${out#,}"
}
# Включён ли приватный листенер в сегменте.
listener_on() {
local seg="$1" s
for s in $PRIV_LISTENERS; do [ "$s" = "$seg" ] && return 0; done
return 1
}
# Действие обучения FDB: eth_src -> порт. Заполняет таблицу 25, то есть делает
# из br-lb обычный обучающийся коммутатор в пределах сегмента.
l2_learn() {
echo "learn(table=25,priority=100,idle_timeout=300,NXM_NX_REG0[]=$1,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[])"
}
# Действие обучения adjacency: по IP-адресу отправителя создаёт в таблице 21
# запись «куда и с каким MAC отправлять ответы этому адресу». Заменяет
# ARP-резолвер, которого у чисто-OpenFlow узла нет. Нужна маршрутизируемому
# трафику: ответ бэкенда из соседнего сегмента приходит к клиенту через L3.
l3_learn() {
echo "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:$1->NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[])"
}
LEARN_PUB=$(l3_learn "$PUB_MAC_H")
# arp_responder <reg0> <ip> <ip_hex> <mac> <mac_hex>
arp_responder() {
echo "table=5,priority=100,reg0=$1,arp,arp_op=1,arp_tpa=$2 actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],mod_dl_src:$4,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:$5->NXM_NX_ARP_SHA[],load:$3->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"
}
# segment_ingress <reg0> <mac_hex шлюза> <сеть> <порты> — таблица 0 для сегмента.
# Обучение FDB идёт для любого кадра, обучение adjacency — только для адресов
# самого сегмента: иначе с аплинка в таблицу 21 попал бы весь внешний мир.
segment_ingress() {
local reg="$1" gwmac_h="$2" net="$3" ports="$4" p
local l2 l3
l2=$(l2_learn "$reg")
l3=$(l3_learn "$gwmac_h")
for p in $ports; do
echo "table=0,priority=200,in_port=$p,ip,nw_src=$net actions=load:$reg->NXM_NX_REG0[],$l2,$l3,goto_table:1"
echo "table=0,priority=190,in_port=$p actions=load:$reg->NXM_NX_REG0[],$l2,goto_table:1"
done
}
# segment_classify <сегмент> <reg0> <mac шлюза> <vip> — таблица 1 для сегмента.
segment_classify() {
local seg="$1" reg="$2" gwmac="$3" vip="$4"
echo "# --- сегмент $seg ---"
if listener_on "$seg"; then
echo "table=1,priority=130,reg0=$reg,dl_dst=$gwmac,tcp,nw_dst=$vip,tp_dst=$PRIV_VIP_PORT actions=goto_table:10"
fi
# Трафик на VIP, для которого листенера нет, отбрасывается со счётчиком —
# так видно, что адрес занят стендом, а листенер выключен.
echo "table=1,priority=125,reg0=$reg,dl_dst=$gwmac,ip,nw_dst=$vip actions=drop"
# Кадр адресован маршрутизатору сегмента: транзит, ответы балансируемых
# сессий из другого сегмента, обращения наружу через LB.
echo "table=1,priority=120,reg0=$reg,dl_dst=$gwmac,ip actions=goto_table:15"
# Всё остальное — трафик между ВМ сегмента: коммутация, при необходимости
# с обратной трансляцией.
echo "table=1,priority=100,reg0=$reg actions=goto_table:24"
}
# segment_untranslate <reg0> <члены> — таблица 24.
segment_untranslate() {
local reg="$1" m ip port
for m in $2; do
eval "ip=\$${m}_IP; port=\$${m}_PORT"
echo "table=24,priority=100,reg0=$reg,tcp,nw_src=$ip,tp_src=$port actions=ct(table=25,zone=$CT_ZONE,nat)"
done
}
cat <<EOF
# =============================================================================
# Таблица 0 — определение сегмента и обучение
# =============================================================================
# Публичный сегмент маршрутизируется как прежде: macvlan-порт один, коммутации
# внутри него нет. Обучение adjacency для него живёт в таблице 1, где известно,
# что трафик адресован стенду.
table=0,priority=200,in_port=$PUB_OFPORT actions=load:0x1->NXM_NX_REG0[],goto_table:1
# Приватные сегменты: каждая ВМ — отдельный порт, br-lb работает коммутатором.
$(segment_ingress "$P2_REG" "$P2_MAC_H" "$P2_NET" "$P2_PORTS")
$(segment_ingress "$P3_REG" "$P3_MAC_H" "$P3_NET" "$P3_PORTS")
table=0,priority=0 actions=drop
# =============================================================================
# Таблица 1 — классификация
# =============================================================================
# ARP обрабатывает собственный респондер (таблица 5): ядро в форвардинге не
# участвует, поэтому штатного ARP-стека у узла нет.
table=1,priority=200,arp actions=goto_table:5
# ICMP echo-request на собственные адреса узла и на VIP — в респондер.
table=1,priority=190,icmp,nw_dst=$LB_PUB_IP,icmp_type=8 actions=goto_table:6
table=1,priority=190,icmp,nw_dst=$VIP,icmp_type=8 actions=goto_table:6
table=1,priority=190,icmp,nw_dst=$LB_P2_IP,icmp_type=8 actions=goto_table:6
table=1,priority=190,icmp,nw_dst=$LB_P3_IP,icmp_type=8 actions=goto_table:6
table=1,priority=190,icmp,nw_dst=$P2_VIP,icmp_type=8 actions=goto_table:6
table=1,priority=190,icmp,nw_dst=$P3_VIP,icmp_type=8 actions=goto_table:6
# Вход из публичного сегмента: запоминаем MAC отправителя как adjacency для
# обратного пути, затем к листенерам. Записи живут 300 с и обновляются каждым
# пакетом клиента.
#
# Обучение включено только для трафика, адресованного стенду (VIP, адрес узла,
# приватные сегменты). Иначе в таблицу 21 попадал бы весь широковещательный
# шум LAN — macvlan-порт видит DHCP, mDNS и SSDP всех соседей по сегменту.
table=1,priority=110,reg0=0x1,ip,nw_dst=$VIP actions=$LEARN_PUB,goto_table:10
table=1,priority=110,reg0=0x1,ip,nw_dst=$LB_PUB_IP actions=$LEARN_PUB,goto_table:10
table=1,priority=110,reg0=0x1,ip,nw_dst=$P2_NET actions=$LEARN_PUB,goto_table:10
table=1,priority=110,reg0=0x1,ip,nw_dst=$P3_NET actions=$LEARN_PUB,goto_table:10
table=1,priority=100,reg0=0x1,ip actions=drop
$(segment_classify p2 "$P2_REG" "$P2_MAC" "$P2_VIP")
$(segment_classify p3 "$P3_REG" "$P3_MAC" "$P3_VIP")
# Всё остальное (не-IP, не-ARP с публичного порта) в прототипе не нужно.
table=1,priority=0 actions=drop
# =============================================================================
# Таблица 5 — ARP-респондер
# =============================================================================
$(arp_responder 0x1 "$LB_PUB_IP" "$LB_PUB_IP_H" "$PUB_MAC" "$PUB_MAC_H")
$(arp_responder 0x1 "$VIP" "$VIP_H" "$PUB_MAC" "$PUB_MAC_H")
$(arp_responder "$P2_REG" "$LB_P2_IP" "$LB_P2_IP_H" "$P2_MAC" "$P2_MAC_H")
$(arp_responder "$P3_REG" "$LB_P3_IP" "$LB_P3_IP_H" "$P3_MAC" "$P3_MAC_H")
# VIP приватных листенеров отвечают тем же MAC, что и шлюз сегмента: по нему
# таблица 1 отличает трафик к маршрутизатору от трафика между ВМ.
$(arp_responder "$P2_REG" "$P2_VIP" "$P2_VIP_H" "$P2_MAC" "$P2_MAC_H")
$(arp_responder "$P3_REG" "$P3_VIP" "$P3_VIP_H" "$P3_MAC" "$P3_MAC_H")
# Адреса источника health-проб: бэкенд резолвит их, отвечая на пробу.
$(arp_responder "$P2_REG" "$HC2_IP" "$HC2_IP_H" "$HC2_MAC" "$HC2_MAC_H")
$(arp_responder "$P3_REG" "$HC3_IP" "$HC3_IP_H" "$HC3_MAC" "$HC3_MAC_H")
# Остальной ARP сегмента — обычная коммутация: ВМ резолвят друг друга сами,
# балансировщик в это не вмешивается.
table=5,priority=10,reg0=$P2_REG actions=goto_table:25
table=5,priority=10,reg0=$P3_REG actions=goto_table:25
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")
$(icmp_responder "$P2_VIP" "$P2_VIP_H" "$P2_MAC_H")
$(icmp_responder "$P3_VIP" "$P3_VIP_H" "$P3_MAC_H")
table=6,priority=0 actions=drop
# =============================================================================
# Таблица 10 — листенеры
# =============================================================================
# multipath раскладывает сессию в один из $SLOTS слотов по симметричному
# L4-хэшу; basis = POOL_ID, поэтому разные пулы дают независимые раскладки.
# Хэш симметричен, то есть прямое и обратное направление дают один слот —
# свойство, необходимое для будущего stateless-датапаса.
#
# Публичный листенер: TCP $VIP:$VIP_PORT.
table=10,priority=200,reg0=0x1,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,reg0=0x1,ip,nw_dst=$VIP actions=drop
# Прочий IP из публичного сегмента маршрутизируется обычным образом
# (клиент может обратиться напрямую к бэкенду, минуя балансировку).
table=10,priority=100,reg0=0x1,ip actions=goto_table:20
EOF
# Приватные листенеры: тот же пул, та же таблица слотов, тот же хэш. Отличие
# только в адресе и в том, что выдача пойдёт коммутацией (таблица 12).
for seg in p2 p3; do
listener_on "$seg" || continue
if [ "$seg" = p2 ]; then reg=$P2_REG; vip=$P2_VIP; else reg=$P3_REG; vip=$P3_VIP; fi
echo "table=10,priority=200,reg0=$reg,tcp,nw_dst=$vip,tp_dst=$PRIV_VIP_PORT actions=multipath(symmetric_l4,$POOL_ID,modulo_n,$SLOTS,0,NXM_NX_REG1[]),goto_table:11"
done
cat <<EOF
table=10,priority=0 actions=drop
# =============================================================================
# Таблица 11 — таблица слотов
# =============================================================================
# Содержимое ($SLOTS правил вида reg1=<слот> -> reg2=<член пула>) рассчитывает
# и заливает демон hcd по Maglev-раскладке живых членов пула. Здесь только
# основание fail-close: пока пул пуст, трафик на VIP отбрасывается со
# счётчиком, а не уходит на заведомо мёртвый бэкенд.
#
# Счётчики правил этой таблицы дают бесплатную per-member статистику.
table=11,priority=0 actions=drop
# =============================================================================
# Таблица 12 — применение выбранного члена пула
# =============================================================================
# Слот положил идентификатор члена в reg2. SNAT не выполняется ни в одном из
# путей: бэкенд видит реальный адрес клиента.
#
# Член в том же сегменте, что и клиент (приоритет 200) — TTL не уменьшается,
# маршрутизации нет. MAC назначения здесь больше не пишется (шаг 5): следующий
# переход — таблица 21, единственное место, где живёт актуальный MAC члена
# (резолвит hcd через ARP, см. hc/neigh.go). Для ВМ клиент и бэкенд остаются
# L2-соседями, какими и были — таблица 21 сама не декрементирует TTL.
EOF
for seg_reg in "$P2_REG:$P2_MEMBERS" "$P3_REG:$P3_MEMBERS"; do
reg="${seg_reg%%:*}"
for m in ${seg_reg#*:}; do
eval "mip=\$${m}_IP; mport=\$${m}_PORT; mid=\$${m}_ID"
echo "table=12,priority=200,reg0=$reg,ip,reg2=$mid actions=ct(commit,zone=$CT_ZONE,nat(dst=$mip:$mport),table=21)"
done
done
cat <<EOF
# Член в другом сегменте либо публичный листенер — обычный путь через
# маршрутизацию: адрес назначения уже переписан, дальше работают таблицы 20/21.
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 — обратный путь L3
# =============================================================================
# ct(nat) без commit снимает ранее выполненный DNAT: source возвращается к VIP.
# Для сессий, которых нет в таблице соединений (транзит между сегментами,
# обращения бэкендов к клиентским префиксам), пакет проходит без изменений.
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 следующего перехода — и для
# маршрутизируемого пути (после таблицы 20), и для коммутируемого (напрямую
# из таблицы 12, без decrement TTL). Три источника записей, по приоритету:
#
# priority=100 члены пула — заливает и обновляет hcd (hc/neigh.go),
# резолвя MAC настоящим ARP через hcif-порты. Пусто до
# первого резолва после старта — fail-close ниже, окно то же,
# что у таблицы слотов.
# priority=90 клиенты (публичные и внутрисегментные) — действие learn из
# таблиц 0 и 1: адрес заранее не известен, изучается пассивно.
# priority=0 неизвестный next-hop — drop.
#
# Ответы на health-пробы — в стек узла через internal-порты; это собственные
# адреса узла, а не next-hop, поэтому остаются статичными.
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
table=21,priority=0 actions=drop
# =============================================================================
# Таблица 24 — обратная трансляция коммутируемого трафика сегмента
# =============================================================================
# Здесь решается задача шага 3. Бэкенд отвечает клиенту-соседу напрямую по L2,
# но кадр всё равно проходит через br-lb, потому что порт ВМ — порт OVS.
# ct(nat) без commit возвращает source к VIP:80 для балансируемых сессий и
# пропускает без изменений всё остальное — обращения be<->be, ответы на
# health-пробы, прямые обращения клиента к бэкенду.
$(segment_untranslate "$P2_REG" "$P2_MEMBERS")
$(segment_untranslate "$P3_REG" "$P3_MEMBERS")
table=24,priority=0 actions=goto_table:25
# =============================================================================
# Таблица 25 — L2-коммутация сегмента
# =============================================================================
# priority=100 — записи FDB, устанавливаемые действием learn из таблицы 0.
# Ниже — рассылка по сегменту: broadcast и multicast отдельным правилом ради
# счётчика, неизвестный unicast — тем же способом. Порт входа OVS исключает
# из рассылки сам.
table=25,priority=50,reg0=$P2_REG,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=$(flood_ports "$P2_PORTS")
table=25,priority=50,reg0=$P3_REG,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=$(flood_ports "$P3_PORTS")
table=25,priority=10,reg0=$P2_REG actions=$(flood_ports "$P2_PORTS")
table=25,priority=10,reg0=$P3_REG actions=$(flood_ports "$P3_PORTS")
table=25,priority=0 actions=drop
EOF