да, но я тестировал на 7.20.2, то есть на момент публикации в рантайме LTS предупреждения ещё не было, только в документации . Получается, RouterOS первым из всех проверенных реализаций добавил настоящую проверку пересечений а остальные шесть молчат по-прежнему
Так всм, если пакет не прошёл ни одного роутера, значит через туннель он и не шёл же. wg0 в бридж не включить, канального уровня у него нет, поэтому путь из локалки к телефону по туннелю обязательно маршрутизируемый и TTL уменьшает. Ноль декрементов означает, что .33 и .32 в этот момент были в одном сегменте то есть телефон сидел в той же локалке по Wi-Fi.
А речь шла про ход через туннель чтобы машина из локалки первой дозвалась до телефона, пока тот на мобильной связи и молчит. Пинг внутри общего сегмента на этот вопрос не отвечает.
Ну так так и есть, оба пункта верны. Прогнал, ядро 6.18.33.2-microsoft-standard-WSL2:
# 1. пирA, и на интерфейсе висит адрес
peerA 10.100.0.2/32
inet 10.100.0.1/24 scope global wg-s
# 2. wg setconf с конфигом, где ТОЛЬКО пир B
rc=0
peerB 10.100.0.3/32
# 3. wg addconf с конфигом, где ТОЛЬКО пир A
rc=0
peerB 10.100.0.3/32
peerA 10.100.0.2/32
# 4. адрес интерфейса после обеих операций
inet 10.100.0.1/24 scope global wg-s
setconf заменил набор пиров целиком, addconf добавил, адрес интерфейса не шелохнулся. Ровно как написано в wg(8): setconf "sets the current configuration of to the contents of ", addconf — "appends the contents … to the current configuration". https://manpages.debian.org/testing/wireguard-tools/wg.8.en.html
А адрес он не трогает именно потому, что о нём не знает: в формате wg(8) ключа Address нет вовсе, он живёт в wg-quick(8) и уходит в ip address add. Это единственное, что я и утверждал.
Так тут адрес назначения не тот. А вопрос был про обратный ход чтобы машина из локалки первой позвала телефон по его туннельному адресу. Здесь назначение 192.168.0.32, снова хост в локалке. Откуда бы этот пинг ни шёл, на заданный вопрос он не отвечает.
И отдельно: ttl=128 без декремента означает, что пакет не прошёл ни одного роутера. Через туннель так не бывает т к wg0 нельзя включить в бридж, у него нет канального уровня вовсе (ARPHRD_NONE, замер парой комментариев выше), поэтому любой путь через WireGuard маршрутизируемый и TTL уменьшает. Ответ Windows-машины, прошедший через хаб, приехал бы со 127.
The configuration file adds a few extra configuration values to the format
understood by wg(8) in order to configure additional attributes of an
interface.
Address — одна из этих extra values, в одном списке с DNS, MTU, Table, PreUp/PostUp и SaveConfig, и описана как "a comma-separated list of IP (v4 or v6) addresses (optionally with CIDR masks) to be assigned to the interface". То есть это то, что уходит в ip address add.
The Interface section may contain the following fields
и полей ровно три PrivateKey, ListenPort, FwMark. Address среди них нет ну и поэтому wg setconf на эту строку и отвечает Line unrecognized, вывод стоит комментарием выше.
Провайдерский работает с внешними UDP-пакетами туннеля. вг ровно для этого и умеет persistent-keepalive: трансляция держится открытой, а endpoint пира подхватывается оттуда, откуда пришёл авторизованный пакет. внутренние адреса при этом не прячутся ни от кого — 10.100.0.2 остаётся 10.100.0.2 по обе стороны туннеля.
Маскарадинг, который вы предлагали Abyss777, другой: он переписывает src уже внутри туннеля. После него дальняя сеть перестаёт существовать под своими адресами, и анонсировать в OSPF становится нечего.
И инициатором в вашем примере всё равно был телефон. Обратный ход — чтобы 192.168.0.33сам, первым, позвал телефон, — вы не показывали. А Abyss777 нужен именно он, в обе стороны и по настоящим адресам. Плюс второго равноценного пути до одной подсети NAT не создаёт ни на каком уровне, а задачка была про это.
Address это директива wg-quick, а не WireGuard, и до ядра она не доезжает вообще. Прогнал, ядро 6.18.33.2-microsoft-standard-WSL2:
#исходный конфиг
[Interface]
PrivateKey = <скрыт>
Address = 10.100.0.2/24
ListenPort = 51820
# wg-quick strip — то, что реально уходит в wg setconf
[Interface]
PrivateKey = <скрыт>
ListenPort = 51820
# а чт если скормить wg конфиг вместе с Address
Line unrecognized: `Address=10.100.0.2/24'
Configuration parsing error
rc=1
wg-quick забирает Address себе под ip address add ( вместе с DNS, MTU, Table и хуками). В WG_CONFIG они не попадают, а cmd_strip() печатает именно WG_CONFIG. Парсер самого wg знает в [Interface] ровно три ключа: PrivateKey, ListenPort и FwMark.
Проверка src на приёме живёт в другом месте wg_allowedips_lookup_src() ищет пира по адресу отправителя внутри туннеля и сравнивает с тем, чьим ключом пакет расшифрован. Не совпало — dishonest_packet_peer и rx_frame_errors (receive.c:404-420). Второго механизма в receive.c нет.
Про "в туннель что угодно" —это про передачу, и тут вы правы т к AllowedIPs выбирают, куда шифровать. просто список один и тот же, и на приёме он же работает фильтром. Одна структура, две работы)
Ну смотри, центр — это хаб, куда сходятся туннели. Дальняя подсеть это локальная сеть за пиром, на котором вы предлагали включить маскарадинг.
Направление такое.хост из дальней подсети инициирует соединение — до центра оно доходит, NAT по дороге создал трансляцию. Центр инициирует первым — не доходит, транслировать ещё нечего. Инициатором в моей фразе был центр, а "сам ничего не инициировал" относилось к дальнему хосту.
Это и несовместимо с задачей Abyss777: ему нужно, чтобы любая точка была достижима из любой, а не только та, которая заговорила первой.
С road-warrior частью согласен полностью: для телефонов и ноутбуков на сервере именно /32 и нужен.
Кроме "только /32". Пир не обязан быть хостом. Если за ним сеть, его AllowedIPs обязаны её включать — иначе обратный трафик туда не уедет, а его собственный src на приёме будет отброшен. Это ровно то, что стоит в каноническом примере wg-quick(8), в разделе EXAMPLES, прямо под словами "For use on a server, the following is a more complicated example involving multiple peers":
И живой случай в этой же ветке — у Abyss777 за пирами несколько подсетей, которые анонсирует OSPF. В /32 их не записать.
По второй половине тоже уточню: на клиенте AllowedIPs не только выбирают, что уйдёт в туннель, но и проверяют src входящего. "Что угодно" работает, пока пир один. Появится второй — будут ровно те же столкновения: ролей в коде нет, lookup один и тот же с обеих сторон.
Направление другое. т к вы проверяете ход изнутри наружу — он работает и с маскарадингом, и без него, ровно для этого NAT и делают. Утверждение было про обратный: дотянуться из центра до хоста в дальней подсети, который сам ничего не инициировал.
Есть ли у вас на этом пути NAT вообще, из пинга не видно, и смотреть надо не пингом. Запустите tcpdump на 192.168.0.33 и гляньте, с каким src приходят пакеты телефона. Туннельный адрес телефона — маскарадинга нет, адресация сквозная. Адрес сервера — есть, и тогда обратный ход с 192.168.0.33 до телефона по его туннельному адресу не пройдёт.
И топология у Abyss777 не ваша.у него за пирами локальные сети, которые анонсирует OSPF, и нужен второй равноценный путь до одной и той же подсети. NAT второго пути не создаёт, куда его ни поставь, а там, где он прячет сеть, она перестаёт быть достижимой по своим адресам — анонсировать её после этого незачем.
Про статистику — принимаю, тут вы правы. из трекеров нельзя вывести, часто это или редко: кто настроил верно, никуда не пишет. Считать по issues распространённость действительно нельзя.
Но аргумент у меня был про другое: не сколько людей наступают, а что они после этого видят. Показателен как раз VyOS: там это завели как "WireGuard cannot configure multiple peers - allowed-ips is overwritten", мейнтейнер механизм назвал верно, задачу закрыли с резолюцией Invalid — и тут же предложили добавить проверку пересечений, правда только в 1.3, чтобы не трогать LTS. То есть даже там, где причину поняли сразу, вывод был aka инструмент должен это проверять. netbird из другой корзины: там это чинили в коде продукта, а не разбирали как ошибку настройки.
Про 10.13.13.2/24 — вы правы, и это моя ошибка в комментарии. В статье нет ни этого примера, ни вообще сюжета про маскирование хост-части: я наткнулся на него уже по ходу обсуждения, а написал все из статьи. Приписал тексту то, чего в нём не было. Извиняюсь (
Про парсер —почти) wg как раз ругается: parse_allowedips зовёт validate_netmask(), та проверяет ненулевые биты хост-части, и при неудаче печатается Warning: AllowedIP has nonzero host part. Беда в том, что предупреждение не фатальное и о последствиях молчит. Ядро копать для этого не нужно — оно нужно на шаг дальше, чтобы понять, что после маскирования пять одинаковых префиксов отберут узел друг у друга и никто об этом не скажет.
Про ip r — хорошее уточнение, и оно, по-моему, работает на ту же мысль. Несколько записей на один префикс таблица маршрутов позволяет, но требует сказать это явно: метрикой, отдельной таблицей, списком nexthop. А голый дубль отвергает — ip route add идёт с NLM_F_CREATE|NLM_F_EXCL и получает File exists; чтобы перебить запись, надо попросить об этом отдельным глаголом, replace. То есть там есть и способ выразить намерение, и отказ, когда оно не выражено. В wg set нет ни того, ни другого: любая запись — replace, и всегда молча.
Про P.S. По коду отказоустойчивости нет ни в каком виде: AllowedIPs не пересматриваются по живости пира вообще. В timers.c нет ни одного обращения к allowedips, а когда попытки handshake исчерпаны, код чистит очередь пакетов и обнуляет ключи — префиксы пира при этом не трогает никто. Отдельно я это мерил на RouterOS, где префикс тоже виден у обоих пиров: удаление второго пира первому префикс не вернуло, понадобился повторный set с тем же самым значением. Так что даже явное удаление не переключает, а недоступность — тем более.
Именно) и маскарадинг тут даже не про тот конец задачи, он переписывает src у того, что уходит в туннель, а пир выбирается по dst т е 10.50.0.0/24 в trie всё равно принадлежит одному пиру, второго пути от NAT не появляется. то есть он лечит в лучшем случае проверку источника на приёме, ценой ровно той сквозной адресации, ради которой всё и строилось.
Он не про тот конец) проблема на передаче: 10.50.0.0/24 в trie принадлежит ровно одному пиру, и NAT этого не меняет — выбор пира по dst остаётся единственным, а значит второго пути как не было, так и нет. Маскарадинг переписывает src у трафика, уходящего в туннель, то есть лечит в лучшем случае проверку источника на приёме, попутно скрывая от центра настоящие адреса дальних сетей.
И заодно ломает то, ради чего там OSPF: NAT работает только для соединений, инициированных изнутри. Достучаться из центра до хоста в дальней подсети после маскарадинга уже нельзя, а анонсировать эти подсети незачем — за NAT их всё равно не видно.
Рабочий вариант это по wg-интерфейсу на соседа и ECMP в таблице маршрутов ядра. Тогда и multipath настоящий, и адресация сквозная и OSPF работает как обычно.
Да, это оно. И вы описали случай точнее, чем я в статье: у вас не ошибка конфигурации, а требование, которое AllowedIPs выразить не может в принципе.
Причина ровно та, что в разборе: trie одна на устройство, и у узла ровно один указатель на пира (device.h:48-50). "Сеть доступна через двух пиров" в такой структуре не записывается — второй wg set просто заберёт префикс себе. Приём ломается симметрично: пакет из дальней подсети, пришедший от «неправильного» пира, отбрасывается как dishonest_packet_peer, потому что lookup_src вернёт другого. ECMP внутри одного wg-интерфейса невозможен не по недосмотру, а по устройству.
Лечится подъёмом ECMP на уровень выше — в таблицу маршрутов ядра, которая multipath умеет: по интерфейсу на соседа, у каждого ровно один пир и AllowedIPs = 0.0.0.0/0. Проверил на живом модуле, ядро 6.18.33.2-microsoft-standard-WSL2. Сразу оговорюсь:это проверка конфигурации и таблицы маршрутов, трафик через туннели я не гонял — эндпоинтов и хендшейка в этом прогоне нет
#1. ОДИН интерфейс, два пира, обоим 10.50.0.0/24 — оба wg set вернули 0
V0lRScKAfhrQ3jlPWb4Htxmy7aV30umdROXVVjVIIQ0= (none)
CYO+1ZvA00gHXFji14e9U/7WTVnmGV8bC3cOyQEeQgk= 10.50.0.0/24
# 2. ДВА интерфейса, по пиру на каждом, обоим 0.0.0.0/0
wg-a: V0lRScKAfhrQ3jlPWb4Htxmy7aV30umdROXVVjVIIQ0= 0.0.0.0/0
wg-b: CYO+1ZvA00gHXFji14e9U/7WTVnmGV8bC3cOyQEeQgk= 0.0.0.0/0
# 3. ip route add 10.50.0.0/24 nexthop dev wg-a weight 1 nexthop dev wg-b weight 1
10.50.0.0/24
nexthop dev wg-a weight 1
nexthop dev wg-b weight 1
# 4. ip route get
10.50.0.7 dev wg-b src 10.0.2.1 uid 0
10.50.0.8 dev wg-a src 10.0.1.1 uid 0
10.50.0.9 dev wg-a src 10.0.1.1 uid 0
10.50.0.10 dev wg-b src 10.0.2.1 uid 0
10.50.0.11 dev wg-b src 10.0.2.1 uid 0
Один и тот же префикс на одном устройстве даёт (none) у первого пира, а на двух устройствах два 0.0.0.0/0 живут спокойно, и ядро раскладывает по ним адреса. Trie у каждого устройства своя — то есть работает это по той же причине, по которой ломалось. Криптоключевая маршрутизация при этом вырождается в " от этого пира принимаем всё", что для транзитного линка и требуется, а путь выбирают ядро и OSPF поверх обычных p2p-интерфейсов. Цена — по интерфейсу и по UDP-порту на соседа, listen-port два wg-устройства не делят.
Что теряется: проверка src средствами AllowedIPs — с /0 она не фильтрует ничего. Если анти-спуфинг нужен, он переезжает в rp_filter или nftables. Со строгим rp_filter при multipath, кстати, всё нормально, вопреки распространённому мнению: __fib_validate_source() зовёт fib_info_nh_uses_dev(), а тот обходит все нексхопы маршрута и принимает пакет, пришедший с любого из них. Strict ломается не на ECMP как таковом, а на настоящей асимметрии, когда обратный маршрут — не тот же multipath.
Если очень хочется остаться на одном интерфейсе — тогда не гонять маршрутизацию через AllowedIPs вообще: WireGuard как транспорт с /32 между эндпоинтами, а внутрь GRE или VXLAN, и OSPF уже на них. тяжелее и с накладными расходами, зато никаких ограничений на динамическую маршрутизацию.
Так с этим я и не спорил ни в одном комментарии. Оба конфига неправильные, это я писал прямым текстом не раз.
Разница между нами ровно одна: вы считаете, что на этом разговор кончается, а я — что он с этого начинается)) Ядро такой конфиг принимает, возвращает 0 и не говорит об этом никому, а увидеть последствия можно только в wg show, куда смотрят последним.
Спасибо за ветку, без иронии. Поправку про изоляцию принял, про панели тоже. обе были по делу.
Прогнал прямо сейчас, ядерный модуль, чистый интерфейс без конфига, без ключа устройства и без трафика:
# ip link add wg-test type wireguard
# A=$(wg genkey | wg pubkey); B=$(wg genkey | wg pubkey)
# wg set wg-test peer $A allowed-ips 10.100.0.2/32
# wg set wg-test peer $B allowed-ips 10.100.0.2/32; echo "rc=$?"
rc=0
# wg show wg-test allowed-ips
2K/oFVzAT40/R1fLGNABLbGRUh/2K8n7pRvPTg5cuyU= (none)
IgOtO/8kgYYd/YIRMe6advAtthVAeXDfYZn5NCW/Cjg= 10.100.0.2/32
Ядро приняло вторую запись, вернуло 0 и ничего не сказало. У первого пира 10.100.0.2/32 больше нет.
Утверждение стоит разделить надвое. как "так не должно быть" — оно верное, и статья с ним не спорит ни в одной строке. Как "так не бывает" — выше консоль. Между не должно быть и ничто не мешает вся статья и помещается.в конфиге на диске у первого пира по-прежнему написано 10.100.0.2/32, в ядре у него (none), и о расхождении не сообщает никто.
wg0 — интерфейс без канального уровня вообще. В wg_setup() стоит dev->type = ARPHRD_NONE, dev->addr_len = 0, dev->hard_header_len = 0, флаги IFF_POINTOPOINT | IFF_NOARP. На живом модуле это выглядит так:
# ip link add wg-test type wireguard
3: wg-test: <POINTOPOINT,NOARP> mtu 1420 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/none
# cat /sys/class/net/wg-test/type
65534
link/none, флаг NOARP, и 65534 — это ARPHRD_NONE числом, уже без участия iproute2 в интерпретации. Ни MAC-адреса, ни ARP, ни кадра. Шифровать на L2 там нечего, потому что L2 там нет — на этом построен и эпизод статьи про rx_frame_errors.
Внутрь туннеля едет тоже не кадр. После расшифровки receive.c смотрит версию IP, и если это не IPv4 и не IPv6 — пакет уходит в dishonest_packet_type. Ethernet внутри WireGuard не ходит принципиально, этим он и отличается от tap-режима OpenVPN или от VXLAN. Снаружи всё это лежит в UDP, то есть в payload L4.
А здесь поправлю сам себя: "криптоключевая маршрутизация не L3" я написал неудачно. Точнее будет — не по таблице маршрутов ядра, а по отдельной привязке префикс -> ключ; уровень при этом ровно L3. src и dst берутся из IP-заголовка и сравниваются с префиксами тем же longest-prefix match. Необычен там не уровень, а то, что роль next-hop играет публичный ключ.
Но L5 из этого всё равно не получается. В самой OSI сетевой уровень определён через логическую адресацию и выбор маршрута, а сеансовый — через управление диалогом между приложениями. Выбор пира по dst-адресу подходит под определение L3 буквально. И писал я не «модель неправильная», а что она тут не помогает: как ни разложи туннель по уровням, на вопрос, почему администратор не узнаёт о перевешивании префикса, модель не отвечает.
Конфиг неправильный— тут спора нет и не было ни разу.
Первым сообщением вы указали, что у пира на сервере /24 вместо /32. Это справедливое замечание к примеру, но не диагноз. на двух одинаковых /32 всё ломается точно так же, мы это выше уже разобрали. Механизм —перевешивание узла при совпадении длины префикса — из "поставьте /32" не выводится.
Про уволить. Наступили на это: репортёр в OPNsense, который в итоге решил, что сам напутал в настройках, и закрыл свой issue; человек, заведший баг в VyOS; разработчики netbird, чинившие ровно этот механизм в продакшн-mesh-VPN 28 июля этого года — refcount был ключован только по префиксу. Cilium и Calico генерируют AllowedIPs списком на узел, и на wg show в кластере не смотрит никто. Это не люди с неправильным конфигом, это люди, которые пишут инструменты.
"Так делать не надо" — верно) и мне в соседней ветке справедливо указали, что это должно стоять в начале статьи, а не в выводах. Спасибо человеку. Только само по себе оно не отвечает на вопрос, почему этого не замечают месяцами.
По первым двум пунктам вы правы, принимаю. "Один префикс — один пир на всём интерфейсе" у меня стоит в выводах, то есть в самом конце, а должно стоять в начале. И жанр не объявлен: читатель сам догадывается, разбор это, багрепорт или предупреждение. Это редакторская ошибка, спорить не буду, надо будет поучиться лучше писвть, благодарен за такое
А про должно намекать поспорю — не логикой, а тем, как оно выглядело у людей. Автор issue в OPNsense закрыл его сам, решив, что напутал в настройках, механизм он так и не узнал. В VyOS завели как баг и закрыли как Invalid. На Server Fault ответ свёлся к "уберите подсети", без объяснения, почему у одних пиров пропадает, а у других нет. netbird чинил это в проде в июле этого года. Логика тут действительно несложная, беда в том, что до неё не доходят.
И grep по конфигу ловит не всё. Вот два способа на одном файле, где у пяти пиров стоит 10.13.13.2/24 … 10.13.13.6/24:
grep -h AllowedIPs wg0.conf | tr -d ’ ’ | sed ‘s/AllowedIPs=//’ | tr ‘,’ ‘\n’ | sort | uniq -d -> пусто, все строки разные
grep -hoP ‘AllowedIPs\s*=\s*\K.*’ wg0.conf | tr -d ’ ’ | tr ‘,’ ‘\n’ | python3 -c ‘import sys,ipaddress for l in sys.stdin: l = l.strip() if l: print(ipaddress.ip_network(l, strict=False))’ | sort | uniq -d -> 10.13.13.0/24
Вся разница в strict=False: он обнуляет хост-часть ровно так же, как это делает wg перед отправкой в ядро. Первый вариант — тот самый grep/awk, о котором вы пишете, и этот случай он не видит.
Со стороны ядра детектор короче:
wg show wg0 allowed-ips | grep ‘(none)’
Пир с (none) — это пир, у которого префикс забрали. Пустой вывод и есть проверка для CI.
Про P.S. Это не гипотетика, такая реализация существует. В BoringTun у пира лежит своя копия списка, и по коду UAPI отдаёт префикс обоим пирам сразу (peer.rs:25 -> api.rs:188); на настоящем типе из крейта это воспроизводится харнессом, сквозного прогона у меня нет, в статье это оговорено. Маршрутизация от такого хранения двойной не становится,пакет всё равно уходит в один туннель, выбор делает общая таблица. То есть "оставить у обоих"— не альтернативная семантика роутинга, а альтернативная семантика отображения, и она хуже. В ядре хотя бы (none) намекает, что что-то произошло; там не намекает ничто. RouterOS ведёт себя так же, и там сверх того удаление второго пира не возвращает префикс первому — нужен повторный set тем же самым значением. вот это уже замерено, на CHR 7.20.2.
да, но я тестировал на 7.20.2, то есть на момент публикации в рантайме LTS предупреждения ещё не было, только в документации . Получается, RouterOS первым из всех проверенных реализаций добавил настоящую проверку пересечений а остальные шесть молчат по-прежнему
Так всм, если пакет не прошёл ни одного роутера, значит через туннель он и не шёл же. wg0 в бридж не включить, канального уровня у него нет, поэтому путь из локалки к телефону по туннелю обязательно маршрутизируемый и TTL уменьшает. Ноль декрементов означает, что .33 и .32 в этот момент были в одном сегменте то есть телефон сидел в той же локалке по Wi-Fi.
А речь шла про ход через туннель чтобы машина из локалки первой дозвалась до телефона, пока тот на мобильной связи и молчит. Пинг внутри общего сегмента на этот вопрос не отвечает.
Ну так так и есть, оба пункта верны. Прогнал, ядро 6.18.33.2-microsoft-standard-WSL2:
setconf заменил набор пиров целиком, addconf добавил, адрес интерфейса не шелохнулся. Ровно как написано в wg(8): setconf "sets the current configuration of to the contents of ", addconf — "appends the contents … to the current configuration". https://manpages.debian.org/testing/wireguard-tools/wg.8.en.html
А адрес он не трогает именно потому, что о нём не знает: в формате wg(8) ключа Address нет вовсе, он живёт в wg-quick(8) и уходит в ip address add. Это единственное, что я и утверждал.
Так тут адрес назначения не тот. А вопрос был про обратный ход чтобы машина из локалки первой позвала телефон по его туннельному адресу. Здесь назначение 192.168.0.32, снова хост в локалке. Откуда бы этот пинг ни шёл, на заданный вопрос он не отвечает.
И отдельно: ttl=128 без декремента означает, что пакет не прошёл ни одного роутера. Через туннель так не бывает т к wg0 нельзя включить в бридж, у него нет канального уровня вовсе (ARPHRD_NONE, замер парой комментариев выше), поэтому любой путь через WireGuard маршрутизируемый и TTL уменьшает. Ответ Windows-машины, прошедший через хаб, приехал бы со 127.
Всм? Это написано в манах, обе страницы открываются в браузере.
man wg-quick(8), секция CONFIGURATION, первая же фраза: https://manpages.debian.org/testing/wireguard-tools/wg-quick.8.en.html
Address — одна из этих extra values, в одном списке с DNS, MTU, Table, PreUp/PostUp и SaveConfig, и описана как "a comma-separated list of IP (v4 or v6) addresses (optionally with CIDR masks) to be assigned to the interface". То есть это то, что уходит в ip address add.
man wg(8), секция CONFIGURATION FILE FORMAT: https://manpages.debian.org/testing/wireguard-tools/wg.8.en.html
Там сказано
The Interface section may contain the following fields
и полей ровно три PrivateKey, ListenPort, FwMark. Address среди них нет ну и поэтому wg setconf на эту строку и отвечает Line unrecognized, вывод стоит комментарием выше.
Это ж два разных NAT на двух разных уровнях.
Провайдерский работает с внешними UDP-пакетами туннеля. вг ровно для этого и умеет persistent-keepalive: трансляция держится открытой, а endpoint пира подхватывается оттуда, откуда пришёл авторизованный пакет. внутренние адреса при этом не прячутся ни от кого — 10.100.0.2 остаётся 10.100.0.2 по обе стороны туннеля.
Маскарадинг, который вы предлагали Abyss777, другой: он переписывает src уже внутри туннеля. После него дальняя сеть перестаёт существовать под своими адресами, и анонсировать в OSPF становится нечего.
И инициатором в вашем примере всё равно был телефон. Обратный ход — чтобы 192.168.0.33сам, первым, позвал телефон, — вы не показывали. А Abyss777 нужен именно он, в обе стороны и по настоящим адресам. Плюс второго равноценного пути до одной подсети NAT не создаёт ни на каком уровне, а задачка была про это.
Address это директива wg-quick, а не WireGuard, и до ядра она не доезжает вообще. Прогнал, ядро 6.18.33.2-microsoft-standard-WSL2:
wg-quick забирает Address себе под ip address add ( вместе с DNS, MTU, Table и хуками). В WG_CONFIG они не попадают, а cmd_strip() печатает именно WG_CONFIG. Парсер самого wg знает в [Interface] ровно три ключа: PrivateKey, ListenPort и FwMark.
Проверка src на приёме живёт в другом месте wg_allowedips_lookup_src() ищет пира по адресу отправителя внутри туннеля и сравнивает с тем, чьим ключом пакет расшифрован. Не совпало — dishonest_packet_peer и rx_frame_errors (receive.c:404-420). Второго механизма в receive.c нет.
Про "в туннель что угодно" —это про передачу, и тут вы правы т к AllowedIPs выбирают, куда шифровать. просто список один и тот же, и на приёме он же работает фильтром. Одна структура, две работы)
Ну смотри, центр — это хаб, куда сходятся туннели. Дальняя подсеть это локальная сеть за пиром, на котором вы предлагали включить маскарадинг.
Направление такое.хост из дальней подсети инициирует соединение — до центра оно доходит, NAT по дороге создал трансляцию. Центр инициирует первым — не доходит, транслировать ещё нечего. Инициатором в моей фразе был центр, а "сам ничего не инициировал" относилось к дальнему хосту.
Это и несовместимо с задачей Abyss777: ему нужно, чтобы любая точка была достижима из любой, а не только та, которая заговорила первой.
С road-warrior частью согласен полностью: для телефонов и ноутбуков на сервере именно /32 и нужен.
Кроме "только /32". Пир не обязан быть хостом. Если за ним сеть, его AllowedIPs обязаны её включать — иначе обратный трафик туда не уедет, а его собственный src на приёме будет отброшен. Это ровно то, что стоит в каноническом примере wg-quick(8), в разделе EXAMPLES, прямо под словами "For use on a server, the following is a more complicated example involving multiple peers":
И живой случай в этой же ветке — у Abyss777 за пирами несколько подсетей, которые анонсирует OSPF. В /32 их не записать.
По второй половине тоже уточню: на клиенте AllowedIPs не только выбирают, что уйдёт в туннель, но и проверяют src входящего. "Что угодно" работает, пока пир один. Появится второй — будут ровно те же столкновения: ролей в коде нет, lookup один и тот же с обеих сторон.
Направление другое. т к вы проверяете ход изнутри наружу — он работает и с маскарадингом, и без него, ровно для этого NAT и делают. Утверждение было про обратный: дотянуться из центра до хоста в дальней подсети, который сам ничего не инициировал.
Есть ли у вас на этом пути NAT вообще, из пинга не видно, и смотреть надо не пингом. Запустите tcpdump на 192.168.0.33 и гляньте, с каким src приходят пакеты телефона. Туннельный адрес телефона — маскарадинга нет, адресация сквозная. Адрес сервера — есть, и тогда обратный ход с 192.168.0.33 до телефона по его туннельному адресу не пройдёт.
И топология у Abyss777 не ваша.у него за пирами локальные сети, которые анонсирует OSPF, и нужен второй равноценный путь до одной и той же подсети. NAT второго пути не создаёт, куда его ни поставь, а там, где он прячет сеть, она перестаёт быть достижимой по своим адресам — анонсировать её после этого незачем.
Про статистику — принимаю, тут вы правы. из трекеров нельзя вывести, часто это или редко: кто настроил верно, никуда не пишет. Считать по issues распространённость действительно нельзя.
Но аргумент у меня был про другое: не сколько людей наступают, а что они после этого видят. Показателен как раз VyOS: там это завели как "WireGuard cannot configure multiple peers - allowed-ips is overwritten", мейнтейнер механизм назвал верно, задачу закрыли с резолюцией Invalid — и тут же предложили добавить проверку пересечений, правда только в 1.3, чтобы не трогать LTS. То есть даже там, где причину поняли сразу, вывод был aka инструмент должен это проверять. netbird из другой корзины: там это чинили в коде продукта, а не разбирали как ошибку настройки.
Про 10.13.13.2/24 — вы правы, и это моя ошибка в комментарии. В статье нет ни этого примера, ни вообще сюжета про маскирование хост-части: я наткнулся на него уже по ходу обсуждения, а написал все из статьи. Приписал тексту то, чего в нём не было. Извиняюсь (
Про парсер —почти) wg как раз ругается: parse_allowedips зовёт validate_netmask(), та проверяет ненулевые биты хост-части, и при неудаче печатается Warning: AllowedIP has nonzero host part. Беда в том, что предупреждение не фатальное и о последствиях молчит. Ядро копать для этого не нужно — оно нужно на шаг дальше, чтобы понять, что после маскирования пять одинаковых префиксов отберут узел друг у друга и никто об этом не скажет.
Про ip r — хорошее уточнение, и оно, по-моему, работает на ту же мысль. Несколько записей на один префикс таблица маршрутов позволяет, но требует сказать это явно: метрикой, отдельной таблицей, списком nexthop. А голый дубль отвергает — ip route add идёт с NLM_F_CREATE|NLM_F_EXCL и получает File exists; чтобы перебить запись, надо попросить об этом отдельным глаголом, replace. То есть там есть и способ выразить намерение, и отказ, когда оно не выражено. В wg set нет ни того, ни другого: любая запись — replace, и всегда молча.
Про P.S. По коду отказоустойчивости нет ни в каком виде: AllowedIPs не пересматриваются по живости пира вообще. В timers.c нет ни одного обращения к allowedips, а когда попытки handshake исчерпаны, код чистит очередь пакетов и обнуляет ключи — префиксы пира при этом не трогает никто. Отдельно я это мерил на RouterOS, где префикс тоже виден у обоих пиров: удаление второго пира первому префикс не вернуло, понадобился повторный set с тем же самым значением. Так что даже явное удаление не переключает, а недоступность — тем более.
Спасибо за фидбек добрый человек!
Именно) и маскарадинг тут даже не про тот конец задачи, он переписывает src у того, что уходит в туннель, а пир выбирается по dst т е 10.50.0.0/24 в trie всё равно принадлежит одному пиру, второго пути от NAT не появляется. то есть он лечит в лучшем случае проверку источника на приёме, ценой ровно той сквозной адресации, ради которой всё и строилось.
Маскарадинг эту задачу не решает.
Он не про тот конец) проблема на передаче: 10.50.0.0/24 в trie принадлежит ровно одному пиру, и NAT этого не меняет — выбор пира по dst остаётся единственным, а значит второго пути как не было, так и нет. Маскарадинг переписывает src у трафика, уходящего в туннель, то есть лечит в лучшем случае проверку источника на приёме, попутно скрывая от центра настоящие адреса дальних сетей.
И заодно ломает то, ради чего там OSPF: NAT работает только для соединений, инициированных изнутри. Достучаться из центра до хоста в дальней подсети после маскарадинга уже нельзя, а анонсировать эти подсети незачем — за NAT их всё равно не видно.
Рабочий вариант это по wg-интерфейсу на соседа и ECMP в таблице маршрутов ядра. Тогда и multipath настоящий, и адресация сквозная и OSPF работает как обычно.
Да, это оно. И вы описали случай точнее, чем я в статье: у вас не ошибка конфигурации, а требование, которое AllowedIPs выразить не может в принципе.
Причина ровно та, что в разборе: trie одна на устройство, и у узла ровно один указатель на пира (device.h:48-50). "Сеть доступна через двух пиров" в такой структуре не записывается — второй wg set просто заберёт префикс себе. Приём ломается симметрично: пакет из дальней подсети, пришедший от «неправильного» пира, отбрасывается как dishonest_packet_peer, потому что lookup_src вернёт другого. ECMP внутри одного wg-интерфейса невозможен не по недосмотру, а по устройству.
Лечится подъёмом ECMP на уровень выше — в таблицу маршрутов ядра, которая multipath умеет: по интерфейсу на соседа, у каждого ровно один пир и AllowedIPs = 0.0.0.0/0. Проверил на живом модуле, ядро 6.18.33.2-microsoft-standard-WSL2. Сразу оговорюсь:это проверка конфигурации и таблицы маршрутов, трафик через туннели я не гонял — эндпоинтов и хендшейка в этом прогоне нет
Один и тот же префикс на одном устройстве даёт (none) у первого пира, а на двух устройствах два 0.0.0.0/0 живут спокойно, и ядро раскладывает по ним адреса. Trie у каждого устройства своя — то есть работает это по той же причине, по которой ломалось. Криптоключевая маршрутизация при этом вырождается в " от этого пира принимаем всё", что для транзитного линка и требуется, а путь выбирают ядро и OSPF поверх обычных p2p-интерфейсов. Цена — по интерфейсу и по UDP-порту на соседа, listen-port два wg-устройства не делят.
Что теряется: проверка src средствами AllowedIPs — с /0 она не фильтрует ничего. Если анти-спуфинг нужен, он переезжает в rp_filter или nftables. Со строгим rp_filter при multipath, кстати, всё нормально, вопреки распространённому мнению: __fib_validate_source() зовёт fib_info_nh_uses_dev(), а тот обходит все нексхопы маршрута и принимает пакет, пришедший с любого из них. Strict ломается не на ECMP как таковом, а на настоящей асимметрии, когда обратный маршрут — не тот же multipath.
Если очень хочется остаться на одном интерфейсе — тогда не гонять маршрутизацию через AllowedIPs вообще: WireGuard как транспорт с /32 между эндпоинтами, а внутрь GRE или VXLAN, и OSPF уже на них. тяжелее и с накладными расходами, зато никаких ограничений на динамическую маршрутизацию.
Согласен, нельзя. Статья не про то, можно ли, а про то, что будет, если это всё же произошло, и как об этом узнать.
Так с этим я и не спорил ни в одном комментарии. Оба конфига неправильные, это я писал прямым текстом не раз.
Разница между нами ровно одна: вы считаете, что на этом разговор кончается, а я — что он с этого начинается)) Ядро такой конфиг принимает, возвращает 0 и не говорит об этом никому, а увидеть последствия можно только в wg show, куда смотрят последним.
Спасибо за ветку, без иронии. Поправку про изоляцию принял, про панели тоже. обе были по делу.
Прогнал прямо сейчас, ядерный модуль, чистый интерфейс без конфига, без ключа устройства и без трафика:
Ядро приняло вторую запись, вернуло 0 и ничего не сказало. У первого пира 10.100.0.2/32 больше нет.
Утверждение стоит разделить надвое. как "так не должно быть" — оно верное, и статья с ним не спорит ни в одной строке. Как "так не бывает" — выше консоль. Между не должно быть и ничто не мешает вся статья и помещается.в конфиге на диске у первого пира по-прежнему написано 10.100.0.2/32, в ядре у него (none), и о расхождении не сообщает никто.
wg0 — интерфейс без канального уровня вообще. В wg_setup() стоит dev->type = ARPHRD_NONE, dev->addr_len = 0, dev->hard_header_len = 0, флаги IFF_POINTOPOINT | IFF_NOARP. На живом модуле это выглядит так:
link/none, флаг NOARP, и 65534 — это ARPHRD_NONE числом, уже без участия iproute2 в интерпретации. Ни MAC-адреса, ни ARP, ни кадра. Шифровать на L2 там нечего, потому что L2 там нет — на этом построен и эпизод статьи про rx_frame_errors.
Внутрь туннеля едет тоже не кадр. После расшифровки receive.c смотрит версию IP, и если это не IPv4 и не IPv6 — пакет уходит в dishonest_packet_type. Ethernet внутри WireGuard не ходит принципиально, этим он и отличается от tap-режима OpenVPN или от VXLAN. Снаружи всё это лежит в UDP, то есть в payload L4.
А здесь поправлю сам себя: "криптоключевая маршрутизация не L3" я написал неудачно. Точнее будет — не по таблице маршрутов ядра, а по отдельной привязке префикс -> ключ; уровень при этом ровно L3. src и dst берутся из IP-заголовка и сравниваются с префиксами тем же longest-prefix match. Необычен там не уровень, а то, что роль next-hop играет публичный ключ.
Но L5 из этого всё равно не получается. В самой OSI сетевой уровень определён через логическую адресацию и выбор маршрута, а сеансовый — через управление диалогом между приложениями. Выбор пира по dst-адресу подходит под определение L3 буквально. И писал я не «модель неправильная», а что она тут не помогает: как ни разложи туннель по уровням, на вопрос, почему администратор не узнаёт о перевешивании префикса, модель не отвечает.
Конфиг неправильный— тут спора нет и не было ни разу.
Первым сообщением вы указали, что у пира на сервере /24 вместо /32. Это справедливое замечание к примеру, но не диагноз. на двух одинаковых /32 всё ломается точно так же, мы это выше уже разобрали. Механизм —перевешивание узла при совпадении длины префикса — из "поставьте /32" не выводится.
Про уволить. Наступили на это: репортёр в OPNsense, который в итоге решил, что сам напутал в настройках, и закрыл свой issue; человек, заведший баг в VyOS; разработчики netbird, чинившие ровно этот механизм в продакшн-mesh-VPN 28 июля этого года — refcount был ключован только по префиксу. Cilium и Calico генерируют AllowedIPs списком на узел, и на wg show в кластере не смотрит никто. Это не люди с неправильным конфигом, это люди, которые пишут инструменты.
"Так делать не надо" — верно) и мне в соседней ветке справедливо указали, что это должно стоять в начале статьи, а не в выводах. Спасибо человеку. Только само по себе оно не отвечает на вопрос, почему этого не замечают месяцами.
По первым двум пунктам вы правы, принимаю. "Один префикс — один пир на всём интерфейсе" у меня стоит в выводах, то есть в самом конце, а должно стоять в начале. И жанр не объявлен: читатель сам догадывается, разбор это, багрепорт или предупреждение. Это редакторская ошибка, спорить не буду, надо будет поучиться лучше писвть, благодарен за такое
А про должно намекать поспорю — не логикой, а тем, как оно выглядело у людей. Автор issue в OPNsense закрыл его сам, решив, что напутал в настройках, механизм он так и не узнал. В VyOS завели как баг и закрыли как Invalid. На Server Fault ответ свёлся к "уберите подсети", без объяснения, почему у одних пиров пропадает, а у других нет. netbird чинил это в проде в июле этого года. Логика тут действительно несложная, беда в том, что до неё не доходят.
И grep по конфигу ловит не всё. Вот два способа на одном файле, где у пяти пиров стоит 10.13.13.2/24 … 10.13.13.6/24:
grep -h AllowedIPs wg0.conf | tr -d ’ ’ | sed ‘s/AllowedIPs=//’ | tr ‘,’ ‘\n’ | sort | uniq -d -> пусто, все строки разные
grep -hoP ‘AllowedIPs\s*=\s*\K.*’ wg0.conf | tr -d ’ ’ | tr ‘,’ ‘\n’ | python3 -c ‘import sys,ipaddress for l in sys.stdin: l = l.strip() if l: print(ipaddress.ip_network(l, strict=False))’ | sort | uniq -d -> 10.13.13.0/24
Вся разница в strict=False: он обнуляет хост-часть ровно так же, как это делает wg перед отправкой в ядро. Первый вариант — тот самый grep/awk, о котором вы пишете, и этот случай он не видит.
Со стороны ядра детектор короче:
wg show wg0 allowed-ips | grep ‘(none)’
Пир с (none) — это пир, у которого префикс забрали. Пустой вывод и есть проверка для CI.
Про P.S. Это не гипотетика, такая реализация существует. В BoringTun у пира лежит своя копия списка, и по коду UAPI отдаёт префикс обоим пирам сразу (peer.rs:25 -> api.rs:188); на настоящем типе из крейта это воспроизводится харнессом, сквозного прогона у меня нет, в статье это оговорено. Маршрутизация от такого хранения двойной не становится,пакет всё равно уходит в один туннель, выбор делает общая таблица. То есть "оставить у обоих"— не альтернативная семантика роутинга, а альтернативная семантика отображения, и она хуже. В ядре хотя бы (none) намекает, что что-то произошло; там не намекает ничто. RouterOS ведёт себя так же, и там сверх того удаление второго пира не возвращает префикс первому — нужен повторный set тем же самым значением. вот это уже замерено, на CHR 7.20.2.