Привет коллегам!
На днях играясь с ng_netflow на тестовой машине нарвался на весьма нетривиальные
эффекты, возникающие при взаимодействии связки из ipfw и netgraph'а с
multicast-трафиком. В процессе разбирательств выяснилось, что причина
загадочного поведения, которое я наблюдал (я его опишу ниже) не имеет отношения
к собственно ng_netflow, а связана с ng_ipfw, через который трафик у меня
попадал в ng_netflow. Поэтому ниже я буду описывать предельно упрощенный setup,
в котором нет ng_netflow, но на котором эти эффекты можно наблюдать.
Итак, имеется FreeBSD 8.2-RELEASE:
#uname -a
FreeBSD tgw.icyb.net.ua 8.2-RELEASE FreeBSD 8.2-RELEASE #0: Fri Feb 18 02:24:46
UTC 2011 [email protected]:/usr/obj/usr/src/sys/GENERIC i386
Загружены следуюшие модули:
#kldstat
Id Refs Address Size Name
1 12 0xc0400000 bd97b4 kernel
2 2 0xc63cc000 11000 ipfw.ko
3 1 0xc63dd000 d000 libalias.ko
4 3 0xc668b000 b000 netgraph.ko
5 1 0xc669b000 4000 ng_socket.ko
6 1 0xc669f000 2000 ng_ipfw.ko
На машине есть такие интерфейсы (показаны только те, что в UP, остальные не
имеют отношения к делу):
#ifconfig -au
em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
options=209b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_MAGIC>
ether 00:19:d1:24:76:c4
media: Ethernet autoselect (1000baseT <full-duplex>)
status: active
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
options=3<RXCSUM,TXCSUM>
inet6 fe80::1%lo0 prefixlen 64 scopeid 0x6
inet6 ::1 prefixlen 128
inet 127.0.0.1 netmask 0xff000000
nd6 options=3<PERFORMNUD,ACCEPT_RTADV>
vlan2: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
options=3<RXCSUM,TXCSUM>
ether 00:19:d1:24:76:c4
inet 192.168.1.141 netmask 0xfffffff0 broadcast 192.168.1.143
media: Ethernet autoselect (1000baseT <full-duplex>)
status: active
vlan: 2 parent interface: em0
vlan3: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
options=3<RXCSUM,TXCSUM>
ether 00:19:d1:24:76:c4
inet 10.1.0.14 netmask 0xfffff000 broadcast 10.1.15.255
media: Ethernet autoselect (1000baseT <full-duplex>)
status: active
vlan: 3 parent interface: em0
ipfw0: flags=8801<UP,SIMPLEX,MULTICAST> metric 0 mtu 65536
На этой машине запущена quagga, в ней сконфигурен и работает OSPF.
OSPF общается с соседями по vlan2, vlan3 в OSPF'е не участвует:
router ospf
ospf router-id 192.168.1.141
redistribute connected metric 25 metric-type 1 route-map CONN
redistribute static metric 30 metric-type 1 route-map NDR
passive-interface default
no passive-interface vlan2
network 192.168.1.128/28 area 0.0.0.0
При этом OSPF, как ему и положено, шлет свои hello и некоторые прочие
пакеты по multicast-адресам 224.0.0.5 и 224.0.0.6 (вот и обещанный
multicast-трафик). Выглядит это так:
#tcpdump -n -i vlan2 proto ospf and src host 192.168.1.141
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vlan2, link-type EN10MB (Ethernet), capture size 96 bytes
09:58:51.151532 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
09:58:57.345173 IP 192.168.1.141 > 224.0.0.6: OSPFv2, LS-Ack, length 44
09:59:01.152235 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
09:59:11.152865 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
09:59:21.153555 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
09:59:28.286692 IP 192.168.1.141 > 224.0.0.6: OSPFv2, LS-Ack, length 64
09:59:31.154143 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
^C
7 packets captured
78 packets received by filter
0 packets dropped by kernel
Таблица маршрутизации до запуска квагги выглядит совсем просто (IPv6 часть
опущена, поскольку к делу не относится):
#netstat -rn
Routing tables
Internet:
Destination Gateway Flags Refs Use Netif Expire
default 10.1.0.1 UGS 6 45 vlan3
10.1.0.0/20 link#8 U 1 49 vlan3
10.1.0.14 link#8 UHS 0 0 lo0
127.0.0.1 link#6 UH 0 2 lo0
192.168.1.128/28 link#7 U 2 54 vlan2
192.168.1.141 link#7 UHS 0 0 lo0
После запуска квагии по OSPF'у приезжает еще ряд маршрутов, которые я тут не
показываю, ибо конкретное их содержание не важно, важен сам факт их наличия.
Важно тут то, что default (который просто прописан ручками в /etc/rc.conf) при
работе квагги сохраняется тем же и продолжает смотреть в интерфейс vlan3 на
10.1.0.1.
(В принципе, по OSPF'у приезжает другой default, но квагга, оставлет в kernel
forwarding table тот, что там был до ее запуска, как более приоритетный. Такая
конфигурация может кому-то показаться странной, но это тестовая машина, тут "на
сейчас" так задумано. Впрочем, это всё детали.)
ipfw сконфигурен предельно просто -- как "open" из стандартного
/etc/rc.firewall:
#ipfw list
00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
00400 deny ip from any to ::1
00500 deny ip from ::1 to any
00600 allow ipv6-icmp from :: to ff02::/16
00700 allow ipv6-icmp from fe80::/10 to fe80::/10
00800 allow ipv6-icmp from fe80::/10 to ff02::/16
00900 allow ipv6-icmp from any to any ip6 icmp6types 1
01000 allow ipv6-icmp from any to any ip6 icmp6types 2,135,136
65000 allow ip from any to any
65535 deny ip from any to any
#sysctl net.inet.ip.fw.one_pass
net.inet.ip.fw.one_pass: 0
Теперь добавляем в картину netgraph:
#ngctl connect ipfw: ipfw: 1 2
#ngctl l -l
There are 2 total nodes:
Name: ipfw Type: ipfw ID: 00000001 Num hooks: 2
Local hook Peer name Peer type Peer ID Peer hook
---------- --------- --------- ------- ---------
2 ipfw ipfw 00000001 1
1 ipfw ipfw 00000001 2
Name: ngctl1894 Type: socket ID: 00000006 Num hooks: 0
(это просто node ng_ipfw, с двумя hook'ами соединенными между собой. На
практике так использовать ng_ipfw, конечно, смысла не имеет, ибо
отправленный а него пакет должен просто тут же вернуться обратно, но для
демонстрации проблемы этого оказывается достаточно)
...и добавляем элементарное правило:
ipfw add 10 netgraph 1 proto ip
В результате добавления этого правила пакеты из него попадают в узел
ng_ipfw и тут же без изменения возвращаются обратно, продолжая путешествовать по
правилам ipfw (one_pass у нас отключен, см. выше). Т.е. повлиять это правило, по
идее, вообще ни на что не должно.
Тот факт, что пакеты в ng_ipfw попадают и нормально возвращаются обратно виден,
например, по синхронному изменению счетчиков на правилах 10 и 65000:
#ipfw -t show 10 65000
00010 334 41895 Tue Jun 14 10:47:54 2011 netgraph 1 ip from any to any
65000 8358 855211 Tue Jun 14 10:47:54 2011 allow ip from any to any
#ipfw -t show 10 65000
00010 345 42991 Tue Jun 14 10:47:56 2011 netgraph 1 ip from any to any
65000 8369 856307 Tue Jun 14 10:47:56 2011 allow ip from any to any
#ipfw -t show 10 65000
00010 358 44243 Tue Jun 14 10:47:58 2011 netgraph 1 ip from any to any
65000 8382 857559 Tue Jun 14 10:47:58 2011 allow ip from any to any
Тем не менее, после добавления этого правила multicast OSPF-пакеты, которые
продолжает слать (или пытаться слать) местный OSPF из vlan2 начисто пропадают
(соответственно, машина выпадает из OSPF, и теряет все полученные ранее
динамические маршруты, а таблица маршрутизации возвращается к девственно чистому
виду, который я приводил выше).
Но что еще более интересно -- эти самые пропавшие multicast-пакеты появляются...
(surprise! surprise!)... во vlan3! Который к OSPF'у вообще никакого отношения не
имеет! Вот как это выглядит:
#tcpdump -n -i vlan3 proto ospf and src host 192.168.1.141
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vlan3, link-type EN10MB (Ethernet), capture size 96 bytes
10:30:00.536356 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
10:30:04.221026 IP 192.168.1.141 > 224.0.0.6: OSPFv2, LS-Ack, length 44
10:30:10.543039 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
10:30:11.399434 IP 192.168.1.141 > 224.0.0.6: OSPFv2, LS-Ack, length 44
10:30:11.409279 IP 192.168.1.141 > 224.0.0.6: OSPFv2, LS-Update, length 64
10:30:12.430221 IP 192.168.1.141 > 224.0.0.6: OSPFv2, LS-Ack, length 44
10:30:20.553881 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
10:30:30.583664 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
10:30:40.599868 IP 192.168.1.141 > 224.0.0.5: OSPFv2, Hello, length 76
^C
9 packets captured
60 packets received by filter
0 packets dropped by kernel
Такой вот весьма неожиданный эффект. Дальнейшее разбирательство показало:
1) если добавлять в ipfw два отдельных правила:
ipfw add 10 netgraph 1 proto ip in
ipfw add 20 netgraph 1 proto ip out
то к указанному эффекту приводит именно out-правило;
2) сборка ядра с отключением FLOWTABLE и добавлением ipfw и netgraph'а
непосредственно в ядро на данный эффект не влияет -- на таком самосборном ядре
наблюдается все то же самое;
3) Изучение исходников ng_ipfw выявило, что когда к нему на какой-то hook
приходит out-пакет (не из ipfw через "ipfw netgraph", а из недр netgraph'а), он
его просто отправляет в ip_output(m, NULL, NULL, IP_FORWARDING, NULL, NULL).
При этом похоже, что ip_output просто тупо маршрутизирует данный пакет (у
которого, напомню, dst IP -- это multicast 224.0.0.X), как если бы dst IP был
обычным unicast-адресом. А поскольку в kernel forwarding table для сети
224.0.0.0/8 никакого явного маршрута нет, то вот оно и отправляется по default'у
во vlan3. Хотя, это только мой wild guess на основании внешних проявлений. До
углубленного штудирования исходников ip_output() я еще не добрался.
В любом случае, поведение неправильное. Что-то где-то надо менять. Интересуют
ваши комментарии по этому поводу. Может кто-то где-то уже с подобным
сталкивался? Или хорошо разбирается в нюансах внутреннего устройства
TCP/IP-стека FreeBSD, и ему очевидно, почему именно оно так себя ведет и как это
можно легко и непринужденно исправить?
В качестве workaround'а, по идее, можно было бы совсем отказаться от ng_ipfw и
просто вешать ng_netflow (ради которого вся эта возня и затевалась изначально)
прямо на все нужные интерфейсы, но тут засада в том, что на той (боевой) машине,
для которой это все предназначено, работет ipfw nat, и хотелось бы, чтобы трафик
при выходе в мир попадал в ng_netflow до за'NAT'ивания, а при входе из мира --
после раз'NAT'ивания. Потому, цеплять ng_netflow непосредственно на внешний
интерфейс не подходит.
--
Olwi