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

113 lines
4.8 KiB
Bash
Executable File

#!/bin/bash
# Инструмент осмотра датапаса. Запускается внутри контейнера lb-router:
# docker compose exec lb-router lbctl <команда>
set -euo pipefail
LB_DIR=/opt/lb
# shellcheck disable=SC1091
source "$LB_DIR/topology.env"
usage() {
cat <<USAGE
lbctl — осмотр датапаса стенда hpnn_v2
ports порты моста и их ofport
flows [табл] правила (все или конкретной таблицы), со счётчиками
health состояние членов пула по данным health-проб
slots раскладка таблицы слотов и счётчики пакетов
drain <член> вывести член из балансировки (пробы продолжаются)
enable <член> вернуть член в балансировку
metrics метрики в формате Prometheus
conns таблица соединений ct (зона $CT_ZONE)
fdb таблица коммутации сегментов: выученные MAC и порты
neigh MAC членов пула, резолвленный hcd через ARP ядра (таблица 21)
trace <src_ip> [src_port] [сегмент]
ofproto/trace для TCP-сессии клиент -> VIP.
Сегмент: pub (по умолчанию), p2, p3 — определяет порт входа
и адрес листенера
reload перегенерировать и применить пайплайн
USAGE
}
api() { curl -fsS --max-time 5 "http://$HC_API$1"; }
case "${1:-}" in
ports)
ovs-vsctl show
ovs-ofctl -O "$OF" show "$BR" | grep -E '^\s+[0-9]+\('
;;
flows)
if [ -n "${2:-}" ]; then
ovs-ofctl -O "$OF" dump-flows "$BR" "table=$2"
else
ovs-ofctl -O "$OF" dump-flows "$BR"
fi
;;
health)
api /status.txt
;;
status)
api /status
;;
slots)
echo "Раскладка по данным датапаса (счётчики правил дают per-member статистику):"
ovs-ofctl -O "$OF" dump-flows "$BR" table=11 \
| sed -n 's/.*n_packets=\([0-9]*\).*set_field:\(0x[0-9a-f]*\)->reg2.*/\2 \1/p' \
| awk '{slots[$1]++; pkts[$1]+=$2}
END {for (m in slots) printf " член id=%s: слотов %d, пакетов %d\n", m, slots[m], pkts[m]}' \
| sort
drops=$(ovs-ofctl -O "$OF" dump-flows "$BR" table=11 | sed -n 's/.*n_packets=\([0-9]*\).*priority=0.*/\1/p')
echo " отброшено правилом fail-close: ${drops:-0}"
api /status.txt | sed -n '2p'
;;
drain)
api "/drain?member=${2:?укажите имя члена}"
;;
enable)
api "/enable?member=${2:?укажите имя члена}"
;;
metrics)
api /metrics
;;
conns)
ovs-appctl dpctl/dump-conntrack | grep -F "zone=$CT_ZONE" || echo "соединений нет"
;;
fdb)
echo "Выученные MAC (таблица 25, reg0 — номер сегмента):"
# priority=100 — только записи, созданные обучением; правила рассылки
# (priority 50 и 10) отбрасываются, иначе маска broadcast выглядела бы
# как выученный MAC.
ovs-ofctl -O "$OF" dump-flows "$BR" table=25 \
| grep 'priority=100' \
| sed -n 's/.*n_packets=\([0-9]*\).*reg0=\(0x[0-9a-f]*\).*dl_dst=\([0-9a-f:]*\).*output:\([0-9]*\).*/ сегмент \2 MAC \3 -> порт \4 (пакетов \1)/p' \
| sort
echo "Рассылка по сегменту (неизвестный MAC и broadcast):"
ovs-ofctl -O "$OF" dump-flows "$BR" table=25 \
| sed -n 's/.*n_packets=\([0-9]*\).*priority=\(50\|10\),reg0=\(0x[0-9a-f]*\).*/ сегмент \3 приоритет \2: пакетов \1/p' \
| sort
;;
neigh)
api /neigh.txt
echo
echo "Правила таблицы 21 (priority=100 — заливает hcd, priority=90 — learn):"
ovs-ofctl -O "$OF" dump-flows "$BR" table=21 | grep -E 'priority=(100|90),ip' | sort
;;
trace)
src_ip="${2:?укажите IP клиента}"
src_port="${3:-40000}"
case "${4:-pub}" in
p2) in_port=$CLI2_OFPORT; gw_mac=$P2_MAC; vip=$P2_VIP; vip_port=$PRIV_VIP_PORT ;;
p3) in_port=$CLI3_OFPORT; gw_mac=$P3_MAC; vip=$P3_VIP; vip_port=$PRIV_VIP_PORT ;;
*) in_port=$PUB_OFPORT; gw_mac=$PUB_MAC; vip=$VIP; vip_port=$VIP_PORT ;;
esac
ovs-appctl ofproto/trace "$BR" \
"in_port=$in_port,dl_src=00:11:22:33:44:55,dl_dst=$gw_mac,dl_type=0x0800,nw_src=$src_ip,nw_dst=$vip,nw_proto=6,nw_ttl=64,tp_src=$src_port,tp_dst=$vip_port,tcp_flags=syn"
;;
reload)
"$LB_DIR/apply.sh"
;;
*)
usage
;;
esac