#!/usr/bin/env bash # lib.sh — общие настройки и утилиты netns-стенда awg-dash (задача #21, эпик #15). # # Подключается через `source` из topology.sh / run.sh. Ничего не выполняет при # подключении, ничего не создаёт — только конфигурация и вспомогательные функции. # # Все имена/адреса переопределяются переменными окружения (см. tests/netns/README.md). # shellcheck shell=bash # --- имена сетевых namespace'ов --------------------------------------------- : "${AWG_NETNS_PREFIX:=awgt21}" : "${AWG_NS_CLIENT:=${AWG_NETNS_PREFIX}-client}" # LAN-клиент за шлюзом : "${AWG_NS_GW:=${AWG_NETNS_PREFIX}-gw}" # хост со шлюзом awg-dash : "${AWG_NS_UPLINK:=${AWG_NETNS_PREFIX}-uplink}" # «ISP» + мок WireGuard-endpoint # --- адресация -------------------------------------------------------------- : "${AWG_NET_CLIENT:=10.213.0.0/24}" : "${AWG_CLIENT_IP:=10.213.0.2}" : "${AWG_GW_LAN_IP:=10.213.0.1}" : "${AWG_NET_WAN:=10.214.0.0/24}" : "${AWG_GW_WAN_IP:=10.214.0.1}" : "${AWG_UPLINK_IP:=10.214.0.2}" # «Провайдер»/мок-endpoint: порт как в wg-quick. : "${AWG_MOCK_PORT:=51820}" # Адрес из TEST-NET-2 (RFC 5737) — «публичный» получатель для детектора утечки. : "${AWG_LEAK_IP:=198.51.100.9}" : "${AWG_LEAK_PORT:=9999}" : "${AWG_LEAK_NET:=198.51.100.0/24}" # --- policy routing --------------------------------------------------------- # Метка и таблица как в wg-quick: fwmark 51820 -> table 51820. : "${AWG_FWMARK:=51820}" : "${AWG_ROUTE_TABLE:=51820}" # --- имена интерфейсов и константы стенда ----------------------------------- : "${AWG_VETH_CLIENT:=veth-c-gw}" : "${AWG_VETH_GW_LAN:=veth-gw-lan}" : "${AWG_VETH_GW_WAN:=veth-gw-wan}" : "${AWG_VETH_UPLINK:=veth-uplink}" NFT_TABLE="awgtest" C_MARK_OUT="awgtest-mark-out" C_MARK_FWD="awgtest-mark-fwd" C_DROP="awgtest-killswitch-drop" IPT_CHAIN_MARK="awgtest-mark" IPT_CHAIN_FWD="awgtest-mark-fwd" IPT_CHAIN_KS="awgtest-ks" # Рабочий каталог стенда (логи мока, sink'а, отчёты). Задаётся run.sh. : "${AWG_NETNS_WORKDIR:=/tmp/awg-netns}" # --- вывод ------------------------------------------------------------------ if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then _c_red=$'\033[31m'; _c_grn=$'\033[32m'; _c_yel=$'\033[33m'; _c_dim=$'\033[2m'; _c_off=$'\033[0m' else _c_red=''; _c_grn=''; _c_yel=''; _c_dim=''; _c_off='' fi log() { printf '%s[netns]%s %s\n' "$_c_dim" "$_c_off" "$*"; } ok() { printf '%s[ ok ]%s %s\n' "$_c_grn" "$_c_off" "$*"; } warn() { printf '%s[warn]%s %s\n' "$_c_yel" "$_c_off" "$*" >&2; } fail() { printf '%s[fail]%s %s\n' "$_c_red" "$_c_off" "$*" >&2; exit 1; } # Код возврата 3 — «заблокировано окружением» (нет root/CAP_NET_ADMIN/ip), # см. deploy/dev-stand.md. 4 — внутренняя ошибка стенда. blocked() { printf '%s[blocked]%s %s\n' "$_c_yel" "$_c_off" "$*" >&2; exit 3; } # --- предусловия ------------------------------------------------------------ require_root() { [ "$(id -u)" -eq 0 ] || blocked "нужен root (или CAP_NET_ADMIN): запустите под sudo" } require_cmd() { command -v "$1" >/dev/null 2>&1 || blocked "нет утилиты '$1' в PATH" } # Проверяем, что ядро/контейнер вообще позволяют netns (иначе AC невыполним). require_netns_capability() { local probe="${AWG_NETNS_PREFIX}-probe" if ! ip netns add "$probe" 2>/dev/null; then blocked "не удалось создать netns — нет CAP_NET_ADMIN/SYS_ADMIN (запустите контейнер с --privileged)" fi ip netns del "$probe" 2>/dev/null || true } ns_exists() { ip netns list | awk '{print $1}' | grep -qx "$1"; } # --- netfilter: метки policy routing и kill-switch -------------------------- # Стенд работает и на nftables (хост Debian/Ubuntu с nft), и на iptables # (alpine-образ приложения: iptables без бинаря nft). Backend выбирается по # наличию бинаря в netns шлюза; семантика правил одинаковая. fw_kind() { # AWG_FW_BACKEND=nft|iptables|auto — принудительный выбор backend (в alpine-образе # приложения есть только iptables, на хосте обычно nft). local want="${AWG_FW_BACKEND:-auto}" case "$want" in nft) ip netns exec "$AWG_NS_GW" sh -c 'command -v nft' >/dev/null 2>&1 && { echo nft; return; } echo none; return ;; iptables) ip netns exec "$AWG_NS_GW" sh -c 'command -v iptables' >/dev/null 2>&1 && { echo iptables; return; } echo none; return ;; esac # command -v — builtin shell, поэтому проверяем через sh -c внутри netns if ip netns exec "$AWG_NS_GW" sh -c 'command -v nft' >/dev/null 2>&1; then echo nft elif ip netns exec "$AWG_NS_GW" sh -c 'command -v iptables' >/dev/null 2>&1; then echo iptables else echo none fi } fw_available() { local kind; kind="$(fw_kind)" case "$kind" in nft) ip netns exec "$AWG_NS_GW" nft list ruleset >/dev/null 2>&1 ;; iptables) ip netns exec "$AWG_NS_GW" iptables -t mangle -L -n >/dev/null 2>&1 ;; *) return 1 ;; esac } # Правило метки для трафика шлюза (OUTPUT) и для транзитного трафика LAN (FORWARD). fw_setup() { local kind; kind="$(fw_kind)" case "$kind" in nft) ip netns exec "$AWG_NS_GW" nft delete table inet "$NFT_TABLE" 2>/dev/null || true ip netns exec "$AWG_NS_GW" nft -f - </dev/null || true $ipt -t mangle -F "$IPT_CHAIN_MARK" $ipt -t mangle -A "$IPT_CHAIN_MARK" -p udp --dport "$AWG_MOCK_PORT" \ -m comment --comment "$C_MARK_OUT" -j MARK --set-mark "$AWG_FWMARK" $ipt -t mangle -C OUTPUT -j "$IPT_CHAIN_MARK" 2>/dev/null \ || $ipt -t mangle -A OUTPUT -j "$IPT_CHAIN_MARK" $ipt -t mangle -N "$IPT_CHAIN_FWD" 2>/dev/null || true $ipt -t mangle -F "$IPT_CHAIN_FWD" $ipt -t mangle -A "$IPT_CHAIN_FWD" -p udp --dport "$AWG_MOCK_PORT" \ -m comment --comment "$C_MARK_FWD" -j MARK --set-mark "$AWG_FWMARK" $ipt -t mangle -C FORWARD -j "$IPT_CHAIN_FWD" 2>/dev/null \ || $ipt -t mangle -A FORWARD -j "$IPT_CHAIN_FWD" $ipt -t filter -N "$IPT_CHAIN_KS" 2>/dev/null || true $ipt -t filter -F "$IPT_CHAIN_KS" $ipt -t filter -A "$IPT_CHAIN_KS" -s "$AWG_NET_CLIENT" -d "$AWG_LEAK_NET" \ -m comment --comment "$C_DROP" -j DROP $ipt -t filter -C FORWARD -j "$IPT_CHAIN_KS" 2>/dev/null \ || $ipt -t filter -A FORWARD -j "$IPT_CHAIN_KS" ;; *) fail "нет ни nft, ни iptables в ${AWG_NS_GW} — стенд не может проверить маркировку" ;; esac log "правила меток/kill-switch установлены (backend: $kind)" } # killswitch on|off — включить/выключить fail-closed правило FORWARD. fw_set_killswitch() { local state="$1" kind; kind="$(fw_kind)" case "$kind:$state" in nft:on) ip netns exec "$AWG_NS_GW" nft delete table inet "$NFT_TABLE" 2>/dev/null || true fw_setup ;; nft:off) ip netns exec "$AWG_NS_GW" nft flush chain inet "$NFT_TABLE" forward_ks ;; iptables:on) fw_setup ;; iptables:off) ip netns exec "$AWG_NS_GW" iptables -t filter -F "$IPT_CHAIN_KS" ;; *) fail "fw_set_killswitch: неизвестное состояние '$state' (ожидается on|off)" ;; esac }