Файрвол управляется кодом из репо AWG-stats: Interfaces/Firewall/{generator,applier,shaper}.py. Применяется через awg-firewall.timer (1 мин, skip если БД-hash не менялся).
Любой ручной
iptables -Iзатрётся через минуту. Все правила должны быть вgenerator.py.
iptables -t raw -A PREROUTING -i awg1 -d 172.30.0.0/16 -j ACCEPT
Docker по умолчанию DROP'ает не-bridge трафик к контейнерам в bridge-сети (Inter-Container Communication). Без этого VPN-юзер → лаб-контейнер = DROP.
iptables -t mangle -A PREROUTING -d 172.16.0.0/12 -j MARK --set-mark 0
iptables -t mangle -A PREROUTING -d 172.16.0.0/12 -j RETURN
... (для 10/8, 192.168/16, и др.)
Почему: CONNMARK restore (для маршрутизации по fwmark через exit-туннели) сохраняет mark на пакет. Без обнуления для приватных сетей пакет к лабе уйдёт через exit-tunnel (eu-nugush), что бессмысленно.
ACCEPT к User.lab_ip (если есть лаба)DROP для серых (iot) — кроме DNS/ICMPDROP для всего labs_subnet от не-владельцевiptables -I DOCKER-USER -d {lab_ip} -s {user_address} -j ACCEPT
iptables -A DOCKER-USER -d {lab_ip} -j DROP # никто кроме владельца
Только idempotent
iptables -C ... || iptables -I ...! Голый-Iбез проверки = дубли. Инцидент 2026-04-17: за 23 дня uptime накопилось 143 465 дублей в DOCKER-USER. Каждый пакет проходил через все. Фикс — idempotent +iptables -F DOCKER-USERдля очистки. 144K → 829 nft-правил.
iptables -t nat -A POSTROUTING -s 172.30.0.0/16 -o {wan} -j MASQUERADE
Лабы выходят в интернет через WAN под IP сервера.
awg_iot — серый списокDROP iot/blocked клиентов к хосту (кроме DNS/ICMP). Применяется в awg_input. Содержит User.address всех с connection_type in ('iot', 'blocked').
awg_rg_<id8> — routing groupsОдин ipset на каждую RoutingGroup в БД (пример: awg_rg_2703c7d9 для группы EU-marketing). В нём — User.address всех участников группы.
ipset restore (T3, 2026-04-23)Раньше: каждый ipset add — отдельный subprocess (N syscalls). Теперь — один вызов:
echo "create awg_rg_<id8>_tmp hash:ip -exist
flush awg_rg_<id8>_tmp
add awg_rg_<id8>_tmp 10.9.0.130
add awg_rg_<id8>_tmp 10.9.0.131
...
create awg_rg_<id8> hash:ip -exist
swap awg_rg_<id8>_tmp awg_rg_<id8>
destroy awg_rg_<id8>_tmp" | ipset restore -exist
Atomic-swap → одна команда. На 10k+ правил даёт заметный прирост.
| Маршрут | fwmark | table | exit |
|---|---|---|---|
| Direct (RU) | 0x1 |
201 | mirkwood WAN |
eu-nugush |
0x2 |
202 | awg0 → nugush |
eu-syun |
0x3 |
203 | awg2 → syun |
eu-sakmara |
0x4 |
204 | awg3 → sakmara |
| Custom routes | 0x100 + index |
(per-route) | — |
Custom routes стартуют с 0x100, потолок ~250. TODO (T9): разделить диапазоны (0x100-0x1FF routes, 0x200+ tunnel marks) — иначе при росте кастомных маршрутов будут коллизии.
ip ruleip rule add fwmark 0x2 table 202
ip rule add fwmark 0x3 table 203
ip rule add fwmark 0x4 table 204
В таблицах — default dev awgX.
При
systemctl restart amneziawg-awgXядро удаляет default-route в table 20X. Drop-inroute.confвосстанавливает (см. routing-recovery).
cd /opt/AmneziaWGStatistic
python3 contrib/bin/apply-firewall.py --force # форс-перегенерация
systemctl status awg-firewall.timer # авто (1 мин)
journalctl -u awg-firewall.service --since '1 hour ago'
# Все цепочки
iptables -L -n -v
iptables -t mangle -L -n -v
iptables -t raw -L -n -v
iptables -t nat -L -n -v
# DOCKER-USER (должно быть < 1000 правил, не 100k)
iptables -L DOCKER-USER -n | wc -l
# ipset
ipset list awg_iot | head
ipset list awg_rg_<id8> | head
Interfaces/Firewall/generator.py / applier.py / shaper.py — код в репо AWG-statsChangelog