Скрипт собирающий netgraph мост для мультикаст трафика (IGMP, UDP) между двумя сетевыми интерфейсами предназначен для замены igmpproxy/mrouted.
mcastbr2.sh
Обсуждения:
FreeBSD maillist
На OpenNet
Создать мост между em0 и re0, где re0 подключён к сети с мультикастом:
mcastbr2.sh start re0 em0
или если нужно чтобы трафик попадал в сетевой стёк ОС, как было до установки моста:
mcastbr2.sh start re0 em0 enable
Удалить мост:
mcastbr2.sh stop re0 em0
Его можно сравнить с LUA: он даёт широкие возможности по манипуляции с сетевыми пакетами, относительно прост в использовании и максимально быстр тк всё происходит в ядре.
У меня было много разных вариантов но в конце мне удалось свести количество нод к двум: ng_ether и ng_bpf.
ng_ether — имеет несколько хуков: lower — это вход/выход сетевого адаптера, upper — ввод/выход в сетевой стёк OS.
ng_bpf — программируемая нода, общий смысл программ: один вход и два выхода: пакет соответствует заданному условии и для пакетов которые не соответствуют. В ноде есть собственный ассемблер для матчинга пакетов. Но можно написать условия для tcpdump и получить код для ng_bpf.
Не большая хитрость ноды в том, что программа устанавливается на входную ноду. Но ноды которые указаны как выходные тоже могут принимать пакеты и обрабатывать они их будут по тем программам которые ассоциированы с ними.
BPF настроен таким образом чтобы пропускать все без исключения пакеты с upper хуков ng_ether нод на lower хуки (пакеты от системы в сеть). Приходящие из сети пакеты с lower хуков нод проверяются в BPF, и
- если это мультикаст;
- и не броадкаст;
- и IGMP [или UDP в случае если пакет от адаптера подключённого к провайдеру]
то такой пакет целиком пересылается на lower хук другого адаптера, минуя сетевой стёк операционной системы.
Либо пакет пересылается на хук ng_hub откуда одна копия уходит на upper хук этого же адаптера, а другая на lower хук противоположного адаптера.
IGMP (IPv4) подпадает под такой фильтр в tcpdump:
ether[0] & 1 = 1 and (ether[0:4] != 0xffffffff or ether[4:2] != 0xffff) and ip[9] = 2
где:
ether[0] & 1 = 1 — проверяем первый байт эзернет пакета, там dst mac адрес, если его первый бит = 1 то пакет multicast или broadcast
(ether[0:4] != 0xffffffff or ether[4:2] != 0xffff) — здесь проверяем что dst mac пакета не broadcast ff:ff:ff:ff:ff:ff. Делается за два сравнения: первое сравнение 4 байта и второе два. Сравнивать 6 байт в одной операции нельзя, ассемблер!
ip[9] = 2 — Проверяем что IP proto = IGMP
ether — означает что смещение и длинна считаются относительно эзернет заголовка, при этом сам bpf не знает если в пакете эзернет заголовок или же он начинается с чего то другого.
ip — это смещение относительно эзернет заголовка, примерно эквивалентно: ip[9] = ether[23], где 23 = 14 + 9, 14 — размер эзернет заголовка. Ещё оно проверяет что ether[12:2] = IP.
В случае инкапсуляции в VLAN придётся переписывать условия.
Теперь получим ассемблерный код для bpf:
tcpdump -i em0 -s 65535 -ddd 'ether[0] & 1 = 1 and ( ether[0:4] != 0xffffffff or ether[4:2] != 0xffff ) and ip[9] = 2'
(имя интерфейса указывать не обязательно, но возможны ошибки тк не все интерфейсы в системе могут работать с tcpdump а берёт первый попавшийся):
13 48 0 0 0 84 0 0 1 21 0 9 1 32 0 0 0 21 0 2 4294967295 40 0 0 4 21 5 0 65535 40 0 0 12 21 0 3 2048 48 0 0 23 21 0 1 2 6 0 0 65535 6 0 0 0
Это и нужно скармливать в ng_bpf чтобы отделить IGMP (IPv4) от всего остального.
IGMP (IPv4) + UDP (IPv4) в tcpdump:
ether[0] & 1 = 1 and (ether[0:4] != 0xffffffff or ether[4:2] != 0xffff) and (ip[9] = 17 or ip[9] = 2)
Всё аналогично, ip[9] = 17 — IP proto = UDP.
И чтобы просто пересылать с upper хуков в lower я использовал такое:
bpf_prog_len=1 bpf_prog=[ { code=6 jt=0 jf=0 k=0 } ]
Это одно строчная «программа» для BPF всегда возвращает NotMatch.
1. Адаптеры нужно перевести в «не разборчивый» = promisc режим, иначе они аппаратно отфильтрую весь мультикат, тк OS не настраивала его пропускание.
ngctl msg ${IF_UPSTREAM}: setpromisc 1 ngctl msg ${IF_DOWNSTREAM}: setpromisc 1
2. autosrc на WAN интерфейсе лучше включить. Таким образом все отбриджованные пакеты будут улетать провайдеру с мак адресом WAN адаптера. Это особенно актуально когда провайдер включает Port Security и задаёт лимит в один адрес.
ngctl msg ${IF_UPSTREAM}: setautosrc 1
3. Моему провайдеру нет дела до того какой src-ip в IP приходит от меня, если бы было, то я бы попробовал гнать трафик в сторону провайдера через ng_patch ноду, которая бы заменяла src-ip на нужны, и выставляла CSUM_IP и CSUM_UDP в заголовке пакета - есть шанс что драйвер сетевого адаптера сам рассчитает эти суммы либо что оборудование провайдера проигнорирует неверную контрольную сумму в IGMP пакетах от меня. Нода также подключается обоими хуками к BPF, выход настраивается на passtrouth (пересылку всех пакетов) на lower хук ng_ether на адаптере в сети провайдера, а вход ng_patch должен быть match выходом от lower на адаптере в локальной сети. Те совсем не большая модификация графа.
4. Работает на vlan интерфейсах.
igmpproxy и mrouted у меня работать отказались: PF по умолчанию убивает все пакеты с IP опциями в заголовке (IGMP они нужны для работы) и поэтому нужно добавлять правило:
pass quick proto igmp from any to 224.0.0.0/4 allow-opts
а я этого не сделал.
После нескольких часов проб, чтения и новых проб я решил не заниматься ремонтом ядра и этих приложений, а просто выборочно сбриджевать два сетевых интерфейса:
Несмотря на цифру 2, по сути эта третья версия графа, получившая в результате оптимизации первых двух.
В первых двух версиях создавалась копия пакета, и один уходил через мост в другую сеть либо отбрасывался, а второй попадал в ядро. Поскольку ядро всё равно их дропало где то внутри и нетграф их очень много отбрасывал, ничего полезного не делая, то я решил их вообще туда не посылать и организовать так чтобы не создавать дубликатов и не уничтожать пакеты.
Для создания аналогичного по функционалу моста, в котором будет несколько сетевых интерфейсов в разных сетях с мультикастом и несколько сетевых адаптеров в сетях куда его нужно переправить потребуется на каждый сетевой адаптер вешать по ng_split + ng_one2many и по одной ng_one2many с каждой стороны моста для рассылки копий мультикаста на все интерфейсы. upper хуки ng_ether нод по прежнему будут напрямую подключатся к BPF. В случае нескольких сетей - источников мультикаста будет ещё проблема с возможным перекрытием адресных пространств, которую можно частично разрешить настроив в BPF фильтрацию по адресам.