Доброе всем время суток.
Выношу на обсуждение мою реализацию шлюза на основе FreeBSD для модуля inet. Здесь я опишу логику шлюза, управление которой будет осуществлять новый модуль. Интеграцию с биллингом будут реализовывать разработчики и это решение в дальнейшем будет опубликовано в вики и станет одним из типовых для биллинга.
В упрощенном виде сеть представляет из себя стандартную трехуровневую модель (см. схему 1).
Вложение:
net.png
Если смотреть в иерархии модуля IPN, то связь такая: DHCP-сервер
-> FreeBSD-шлюз
->Абонентский свитч.
Схема работает по принципу VLAN на абонента + IP Unnumbered (SuperVLAN). Сразу оговорюсь, что шлюз предполагает терминирование полных 4096 VLAN ID на
каждый сетевой интерфейс в системе. Т.е. недостатка VLAN ID не предвидится. Скорее у вас закончится мощности по маршрутизации трафика, чем VLAN ID.
Минимальный функционал абонентского свитча: 802.1q VLAN, DHCP Relay с option 82, ssh\telnet управление. Естественно нелишним будет как минимум loop guard, RSTP, acl, port isolation (private vlan). Для IPTV - igmp snooping и MVR.
Минимальный функционал магистрального свитча:802.1q VLAN trunking (т.е. свитч должен уметь через себя пропускать все 4096 VLAN-ов). Собственно от него требуется только собирать гроздь VLAN-ов абонентских свитчей и передавать это все на шлюз. Для IPTV обязательно нужен igmp snooping.
Абонент получает IP-адрес с помощью DHCP Option 82. Идентификатором абонента является либо ID-свитча+VLAN ID, либо ID-свитча+VLAN ID+MAC.
Вся терминация абонентских VLAN-ов на шлюзе сделана в ядре с помощью netgraph (см. схему 2).
Вложение:
dot.gif
На схеме 2 и в примерах конфигурации ниже, предполагается, что интерфейс em1 смотрит в сторону абонентов, а интерфейс em2 в сторону ядра сети. Скрипт написан без учета теминирования VLAN более чем на одном интерфейсе (что не сложно дописать). Скрипт создает всю цепочку netgraph, создает абонентские VLAN-интерфейсы, переименовывает их более удобоваримый вид. Стоит выделить то, что все, явно несозданные VLAN-ы, прозрачно ходят между em1 и em2. Это полезно, например, если вы в ядре имеете PPPoE-сервер для смешанных сетей (в период модернизации или невыгодно на дом ставить полноценный свитч) или хотите оказать юрику услугу по объединению офисов или опять же пропустить прозрачно VLAN с мультикаст-трафиком.
Скрипт запускается на старте из /etc/rc.local
Код:
#!/bin/sh
# VARIABLES
# -- Первый пользовательский VLAN
FIRST_VLAN="1001"
# -- Последний пользовательский VLAN
LAST_VLAN="4001"
# -- Собственный адрес шлюза, через который будет осуществляться маршрутизация
GATEWAY_ADDR="cc.19.64.21"
# -- Адрес для управления шлюза
MANAGMENT_ADDR="192.168.129.59"
# -- Адрес коллектора netflow-потока
NETFLOW_COLLECTOR="aa.200.144.93:3000"
# -- Номер ngeth-интерфейса, с которого начинается пользовательские VLAN'ы
# -- Нумерация с нуля. Ниже до основного цикла создаются два VLAN-а для
# -- собственных нужд, соответственно нумерация клиентских пойдет с 2.
FIRST_NGETH_TO_RENAME="2"
# PROGRAM PATH
NGCTL=/usr/sbin/ngctl
IFCONFIG=/sbin/ifconfig
ROUTE=/sbin/route
EXPR=/bin/expr
GREP=/usr/bin/grep
AWK=/usr/bin/awk
MAC_ADDR=`$IFCONFIG em1 | $GREP ether | $AWK '{print $2}'`
$NGCTL -f - << EOF
mkpeer em1: netflow lower iface0
name em1:lower netflow
mkpeer netflow: vlan out0 downstream
name netflow:out0 vlan_em1
msg netflow: setconfig { iface=0 conf=11 }
msg netflow: settimeouts { inactive=15 active=60 }
mkpeer em2: vlan lower downstream
name em2:lower vlan_em2
connect vlan_em1: vlan_em2: nomatch nomatch
mkpeer vlan_em2: eiface vlan11 ether
name vlan_em2:vlan11 vlan11
msg vlan_em2: addfilter { vlan=11 hook="vlan11" }
mkpeer vlan_em1: bridge vlan1 link1
name vlan_em1:vlan1 bridge0
connect vlan_em2: bridge0: vlan1 link2
mkpeer bridge0: eiface link0 ether
name bridge0:link0 vlan1
msg vlan_em1: addfilter { vlan=1 hook="vlan1" }
msg vlan_em2: addfilter { vlan=1 hook="vlan1" }
msg em1: setpromisc 1
msg em2: setpromisc 1
msg em1: setautosrc 0
msg em2: setautosrc 0
EOF
VLAN=$FIRST_VLAN
while [ "$VLAN" -lt "$LAST_VLAN" ]
do
$NGCTL mkpeer vlan_em1: eiface vlan$VLAN ether
$NGCTL name vlan_em1:vlan$VLAN em1_$VLAN
$NGCTL msg vlan_em1: addfilter \{ vlan=$VLAN hook=\"vlan$VLAN\" \}
VLAN=`$EXPR $VLAN + 1`
done
$IFCONFIG ngeth0 name vlan11
$IFCONFIG vlan11 down
$IFCONFIG vlan11 ether `$IFCONFIG em2 | $GREP ether | $AWK '{print $2}'`
$IFCONFIG vlan11 inet $GATEWAY_ADDR netmask 255.255.255.0 up
$IFCONFIG ngeth1 name vlan1
$IFCONFIG vlan1 down
$IFCONFIG vlan1 ether $MAC_ADDR
$IFCONFIG vlan1 inet $MANAGMENT_ADDR netmask 255.255.240.0 up
$NGCTL mkpeer netflow: ksocket export inet/dgram/udp
$NGCTL msg netflow:export bind inet/$GATEWAY_ADDR
$NGCTL msg netflow:export connect inet/$NETFLOW_COLLECTOR
VLAN=$FIRST_VLAN
NGETH=$FIRST_NGETH_TO_RENAME
while [ "$VLAN" -lt "$LAST_VLAN" ]
do
$IFCONFIG ngeth$NGETH name em1.$VLAN
$IFCONFIG em1.$VLAN ether $MAC_ADDR
$IFCONFIG em1.$VLAN inet $GATEWAY_ADDR netmask 255.255.255.255 up
VLAN=`$EXPR $VLAN + 1`
NGETH=`$EXPR $NGETH + 1`
done
$ROUTE del $GATEWAY_ADDR/32
Про подсистему netgraph можно почитать например здесь:
http://habrahabr.ru/blogs/bsdelniki/86553/Для обмена маршрутами между шлюзами и другими маршрутизаторами ядра используется пакет Quagga, в котором мы используем zebra и bgpd/ospfd. Нужно отметить, что для корректной работы конструкции Unnumbered IP (SuperVLAN) нужно в таблицу маршрутизации прописывать руками маршруты вида:
Код:
route add -host cc.19.64.111 -iface em1.1234
где cc.19.64.111 ip абонента, а em1.1234 интерфейс (1234 - номер VLAN-а). Т.к. у нас используется zebra, маршруты будем прописывать в ней, а zebra уже сама их внесет в системную таблицу маршрутизации:
Код:
zebra# conf t
zebra(conf)# ip route cc.19.64.111 em1.1234
zebra(conf)# end
Соответственно и удалять нужно в zebra:
Код:
zebra# conf t
zebra(conf)# no ip route cc.19.64.111 em1.1234
zebra(conf)# end
Для того, чтобы от этого избавиться, нужно заставить биллинг каким-то образом самому прописывать маршруты на нужном шлюзе в zebra. Если смотреть на функционал unnumbered ip, который реализован на L3-свитчах Cisco, то видно, что там маршруты прописываются по триггеру выдачи ip-адреса DCHP-сервером, соответственно при истечении срока аренды, маршрут удаляется, если он не был продлен. Т.е. по аналогии с этим - самое лучшее место это DCHP-сервер биллинга, экземпляр которого должен стоять на каждом шлюзе. DCHP-сервер в момент отдачи адреса клиенту должен выполнить команду по добавлению маршрута в zebra. Далее, по истечению срока аренды адреса (если клиент его не обновил) DHCP-сервер должен маршрут из zebra удалить. DHCP-сервер должен периодически и по выходу из программы сохранять таблицу ip-адресов с неистекщим сроком аренды на диск, а по запуску загружать эту таблицу. Также адрес досрочно должен удалиться из таблицы и выполнится команда удаления маршрута, если с биллинга придет новый xml без этого адреса. Для конфигурирования zebra проще всего будет воспользоваться утилитой vtysh, которая входит в пакет Quagga:
Код:
root@gw# vtysh -d zebra -c 'conf t' -c 'ip route cc.19.64.111 em1.1234' -c 'end'
При изучении исходников IPN DCHP-сервера, было обнаружено, что бОльшая часть работы по реализации такого функционала там уже выполнена. Дело за малым. Собственными силами DHCP-сервер уже дописан и при необходимости изменения будут переданы разработчикам биллинга.
Еще одно замечание: В ПО Quagga на момент написания этого текста существует баг под
номером 494, из-за которого на FreeBSD не добавляется маршрут в системную таблицу маршрутизации, если в качестве адреса назначения указано имя интерфейса. Баг описан, подтвержден и есть сторонний патч, исправляющий эту проблему, однако разработчики не спешат исправлять проблему и выпускать новый релиз, поэтому порт Quagga придется пропатчить и перекомпилировать самому. Для этого достаточно закинуть патч в каталог files в каталоге с портом quagga, поправить в патче пути и сделать make install clean
Дополнительный опции, с которыми нужно скомпилировать новое ядро:
Код:
options IPFIREWALL
options IPFIREWALL_VERBOSE
options IPFIREWALL_VERBOSE_LIMIT=100
options IPFIREWALL_FORWARD
options HZ=1000
options IPFIREWALL_DEFAULT_TO_ACCEPT
options DUMMYNET
/boot/loader.conf
Код:
netgraph_load="YES"
ng_ether_load="YES"
ng_vlan_load="YES"
ng_netflow_load="YES"
ng_ksocket_load="YES"
/etc/sysctl.conf
Код:
net.inet.ip.dummynet.io_fast=1
net.inet.ip.fw.one_pass=1
net.inet.ip.dummynet.hash_size=65535
net.inet.ip.dummynet.expire=0
/etc/rc.conf
Код:
firewall_enable="YES"
firewall_script="/etc/rc.ipfw"
gateway_enable="YES"
quagga_enable="YES"
quagga_flags="-d -A 127.0.0.1"
quagga_daemons="zebra bgpd"
Естественно, что указываются только важные опции, остальные по вкусу.
Как вы успели заметить выше, шейпирование скорости на данном этапе планируется с помощью dummynet. Честно говоря, это спорный выбор, но он отличается элегантностью реализации и простотой интеграции с биллингом, в отличии от ng_car. Реальную разницу нужно смотреть под нагрузкой, однако, как мне кажется, при отсутствии NAT на шлюзе (а NAT-ферма имхо должна стоять где-то поближе к бордеру сети) упереться в производительность dummynet не должны.
/etc/rc.ipfw
table 11: ip-адреса абонентов, которым запрещено все, кроме доступа на страничку своего счета. При попытке загрузить любую страничку в интернете выдается страница с ошибкой и просьбой поплнить счет и т.п. Аргумент игнорируется.
table 1: ip-адреса абонентов, у которых нужно шейпить входящий внешний трафик. В качестве аргумента указывается номер pipe с нужной скоростью. Используются pipe с mask dst-ip.
table 2: ip-адреса абонентов, у которых нужно шейпить входящий локальный трафик. В качестве аргумента указывается номер pipe с нужной скоростью. Используются pipe с mask dst-ip.
table 3: ip-адреса абонентов, у которых нужно шейпить исходящий внешний трафик. В качестве аргумента указывается номер pipe с нужной скоростью. Используются pipe с mask src-ip.
table 4: ip-адреса абонентов, у которых нужно шейпить исходящий локальный трафик. В качестве аргумента указывается номер pipe с нужной скоростью. Используются pipe с mask src-ip.
При необходимости, по аналогии создается больше классов трафиков, которые отдельно нужно зажимать.
Обращаю внимание на то, что трубы независимы друг от друга как в плане разных абонентов, так и в плане типов трафика ,которые они зажимают. Т.е. например можно одновременно качать с максимальной скоростью трубы из внешнего интернета и тут же качать с максимальной скоростью трубы для локального трафика из локалки.
pipe 1 и 2 созданы как заглушка для биллинга, который должен устанавливать хоть какое-то правило, даже при отсутствии ограничения на скорость.
Код:
#!/bin/sh
/sbin/ipfw -q flush
/sbin/ipfw -q add 00010 allow icmp from table\(11\) to aa.200.144.3 icmptype 0,8
/sbin/ipfw -q add 00020 allow icmp from table\(11\) to aa.200.149.3 icmptype 0,8
/sbin/ipfw -q add 00030 allow icmp from table\(11\) to aa.200.144.11 icmptype 0,8
/sbin/ipfw -q add 00030 allow icmp from table\(11\) to bb.198.216.10 icmptype 0,8
/sbin/ipfw -q add 00040 allow tcp from table\(11\) to aa.200.144.3 53
/sbin/ipfw -q add 00050 allow udp from table\(11\) to aa.200.144.3 53
/sbin/ipfw -q add 00060 allow tcp from table\(11\) to aa.200.149.3 53
/sbin/ipfw -q add 00070 allow udp from table\(11\) to aa.200.149.3 53
/sbin/ipfw -q add 00080 allow tcp from table\(11\) to aa.200.144.11 8080
/sbin/ipfw -q add 00090 allow tcp from table\(11\) to aa.200.144.11 8443
/sbin/ipfw -q add 00100 fwd 192.168.128.3 ip from table\(11\) to any 80
/sbin/ipfw -q add 00110 unreach net-prohib ip from table\(11\) to any
/sbin/ipfw -q add 00510 allow ip from aa.200.144.3 to table\(11\)
/sbin/ipfw -q add 00520 allow ip from aa.200.149.3 to table\(11\)
/sbin/ipfw -q add 00530 allow ip from aa.200.144.11 to table\(11\)
/sbin/ipfw -q add 00540 allow icmp from bb.198.216.10 to table\(11\)
/sbin/ipfw -q add 00550 allow tcp from any 80 to table\(11\)
/sbin/ipfw -q add 00560 unreach host-prohib ip from any to table\(11\)
/sbin/ipfw -q add 01010 pipe tablearg ip from aa.200.144.0/20 to table\(2\) out
/sbin/ipfw -q add 01020 pipe tablearg ip from bb.198.216.0/21 to table\(2\) out
/sbin/ipfw -q add 01030 pipe tablearg ip from cc.19.64.0/21 to table\(2\) out
/sbin/ipfw -q add 01040 pipe tablearg ip from 172.16.0.0/12 to table\(2\) out
/sbin/ipfw -q add 01050 pipe tablearg ip from 10.16.0.0/12 to table\(2\) out
/sbin/ipfw -q add 01060 pipe tablearg ip from xx.154.64.0/18 to table\(2\) out
/sbin/ipfw -q add 01070 pipe tablearg ip from any to table\(1\) out
/sbin/ipfw -q add 02010 pipe tablearg ip from table\(4\) to aa.200.144.0/20 in
/sbin/ipfw -q add 02020 pipe tablearg ip from table\(4\) to bb.198.216.0/21 in
/sbin/ipfw -q add 02030 pipe tablearg ip from table\(4\) to cc.19.64.0/21 in
/sbin/ipfw -q add 02040 pipe tablearg ip from table\(4\) to 172.16.0.0/12 in
/sbin/ipfw -q add 02050 pipe tablearg ip from table\(4\) to 10.16.0.0/12 in
/sbin/ipfw -q add 02060 pipe tablearg ip from table\(4\) to xx.154.64.0/18 in
/sbin/ipfw -q add 02070 pipe tablearg ip from table\(3\) to any in
/sbin/ipfw -q pipe flush
/sbin/ipfw pipe 1 config mask dst-ip 0xffffffff buckets 512 queue 100
/sbin/ipfw pipe 2 config mask src-ip 0xffffffff buckets 512 queue 100
/sbin/ipfw pipe 64 config mask dst-ip 0xffffffff bw 64Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 65 config mask src-ip 0xffffffff bw 32Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 128 config mask dst-ip 0xffffffff bw 128Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 129 config mask src-ip 0xffffffff bw 64Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 256 config mask dst-ip 0xffffffff bw 256Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 257 config mask src-ip 0xffffffff bw 128Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 96 config mask dst-ip 0xffffffff bw 96Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 97 config mask src-ip 0xffffffff bw 96Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 192 config mask dst-ip 0xffffffff bw 192Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 193 config mask src-ip 0xffffffff bw 192Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 384 config mask dst-ip 0xffffffff bw 384Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 385 config mask src-ip 0xffffffff bw 384Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 512 config mask dst-ip 0xffffffff bw 512Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 513 config mask src-ip 0xffffffff bw 512Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 100 config mask dst-ip 0xffffffff bw 100Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 101 config mask dst-ip 0xffffffff bw 200Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 102 config mask src-ip 0xffffffff bw 100Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 200 config mask dst-ip 0xffffffff bw 200Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 201 config mask dst-ip 0xffffffff bw 400Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 202 config mask src-ip 0xffffffff bw 200Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 300 config mask dst-ip 0xffffffff bw 300Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 301 config mask dst-ip 0xffffffff bw 600Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 302 config mask src-ip 0xffffffff bw 300Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 400 config mask dst-ip 0xffffffff bw 400Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 401 config mask dst-ip 0xffffffff bw 800Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 402 config mask src-ip 0xffffffff bw 400Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 500 config mask dst-ip 0xffffffff bw 500Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 501 config mask dst-ip 0xffffffff bw 1000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 502 config mask src-ip 0xffffffff bw 500Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 700 config mask dst-ip 0xffffffff bw 700Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 701 config mask dst-ip 0xffffffff bw 1400Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 702 config mask src-ip 0xffffffff bw 700Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 1000 config mask dst-ip 0xffffffff bw 1000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 1001 config mask src-ip 0xffffffff bw 1000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 1500 config mask dst-ip 0xffffffff bw 1500Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 1501 config mask src-ip 0xffffffff bw 1500Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 2000 config mask dst-ip 0xffffffff bw 2000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 2001 config mask src-ip 0xffffffff bw 2000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 650 config mask dst-ip 0xffffffff bw 650Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 651 config mask dst-ip 0xffffffff bw 1300Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 652 config mask src-ip 0xffffffff bw 650Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 800 config mask dst-ip 0xffffffff bw 800Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 801 config mask dst-ip 0xffffffff bw 1600Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 802 config mask src-ip 0xffffffff bw 800Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 1010 config mask dst-ip 0xffffffff bw 1000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 1011 config mask dst-ip 0xffffffff bw 1000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 1012 config mask src-ip 0xffffffff bw 1000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 3000 config mask dst-ip 0xffffffff bw 3000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 3001 config mask dst-ip 0xffffffff bw 3000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 3002 config mask src-ip 0xffffffff bw 3000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 5000 config mask dst-ip 0xffffffff bw 5000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 5001 config mask dst-ip 0xffffffff bw 5000Kbit/s buckets 512 queue 100
/sbin/ipfw pipe 5002 config mask src-ip 0xffffffff bw 5000Kbit/s buckets 512 queue 100
Т.е. фактическое занятие биллинга, это прописывать в те или иные таблицы ip-адреса абонентов этого шлюза:
Код:
root@gw# ipfw table 1 delete cc.19.64.111
root@gw# ipfw table 1 add cc.19.64.111 5000
root@gw# ipfw table 2 delete cc.19.64.111
root@gw# ipfw table 2 add cc.19.64.111 5001
root@gw# ipfw table 3 delete cc.19.64.111
root@gw# ipfw table 3 add cc.19.64.111 5002
root@gw# ipfw table 4 delete cc.19.64.111
root@gw# ipfw table 4 add cc.19.64.111 2
Управление абонентским свитчом осуществляется через telnet\ssh, причем биллинг должен производить мониторинг на изменения в конфигурации и изменять ее только в случае ее реальной смены. Алгоритм, что сейчас у нас заложен в шлюзе IPN, я опишу попозже.
В общем это пока все. Обсуждение крайне приветствуется.