Балансировка 2-х и более каналов на FreeBSD с использованием PF + Squid

Опубликовано:

Итак, о задаче: есть два канала интернет, шлюз на FreeBSD

gate# uname -a
FreeBSD gate 9.0-RELEASE FreeBSD 9.0-RELEASE #0: Thu Nov  1 06:48:52 OMST 2012     root@gate:/usr/obj/usr/src/sys/GATE  amd64

Не то, чтобы необходимость, но желание создать гибкую систему с балансировкой трафика по каналам и желание получить премию от руководства.
Канал №1: безлимитка, скорость 7 Мб, реальный ip-адрес
Канал №2: безлимитка, скорость до 60Мб, реальный ip-адрес.
Со стороны провайдера были установлены шлюзы, через которые реализую DMZ на «ловушки» для хакеров, поэтому настройки PF и SQUID минимальны

Описание поднятия балансировки

Все манипуляции от пользователя root. (Подключаемся к хосту любым пользователем, затем su, пароль root).

1. Опции ядра для включения PF:
Если не считать трафик средствами PF, то второй пункт можно отключить

cd /sys/amd64/conf
cp GENERIC GATE
ee GATE

device          pf
device          pflog
options ALTQ
options ALTQ_CBQ
options ALTQ_RED
options ALTQ_RIO
options ALTQ_HFSC
options ALTQ_PRIQ
options ALTQ_NOPCC

2. сборка ядра

make kernel KERNCONF=GATE

3. В шлюз установлены 2 сетевых карты, одна интегрированная в материнскую плату:

re0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=389b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_UCAST,WOL_MCAST,WOL_MAGIC>
        inet 192.168.1.2 netmask 0xffffff00 broadcast 192.168.1.255
re1: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=389b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_UCAST,WOL_MCAST,WOL_MAGIC>
        inet 192.168.63.26 netmask 0xfffffff8 broadcast 192.168.63.31
nfe0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=82008<VLAN_MTU,WOL_MAGIC,LINKSTATE>
        inet 10.1.1.30 netmask 0xffffff00 broadcast 10.1.1.255 

re0 провайдер №1, re1 — провайдер №2. Внутренний интерфейс nfe0 (назначение ip адресов и масок сложилось исторически, я работаю с тем, что есть). И конечно же подняты vlan на внутреннем интерфейсе, раскидываю по ним систему ip видеонаблюдения и различные отделы компании.

4. Настройка rc.conf

ee /etc/rc.conf
pf_enable=«YES»
pf_rules=«/etc/pf.conf»
squid_enable=«YES»
reboot

То есть при загрузке выполняются правила из файла /etc/pf.conf.

5. Листинг pf.conf.

cat pf.conf
#Задаем переменные, где int_if - внутренний интерфейс, ext_if - внешние.
int_if=«nfe0»
ext_if=«re1»
ext_if2=«re0»
int_net=«10.1.1.0/24»
freeBSD=«10.1.1.30»
icmp_types=«{ echoreq, unreach}»
http=«80»
https=«443»
ssh=«22»

#описываем шлюзы для каждого из провайдеров.
gw1=«192.168.63.25»
gw2=«192.168.1.1»

#Для тех, кто должен идти с конкретного провайдера вводим принудительное направление траффика в обход балансировки.
#Например, клиент-банки или тендерные площадки чувствительны к смене ip-адреса при балансировке.
to_ertel=«{10.1.1.42, 10.1.1.33, 10.1.1.4, 10.1.1.12, 10.1.1.5, 10.1.1.48, 10.1.1.25, 10.1.1.3, 10.1.1.243, 10.1.1.5 }»
to_trans=«{ 10.1.1.27, 10.1.1.2, 10.1.1.181, 10.1.1.46, 10.1.1.39, 10.1.1.27, 10.1.1.31, 10.1.1.113 }»

#настройки политик файрвола - всех из вне посылать,
set block-policy drop
>set skip on lo
#собирать все пакеты перед их отправкой.
scrub in all

#Нат на первый интернет-вход
nat on $ext_if inet from any to any -> ($ext_if)
#Нат на второй интернет-вход
nat on $ext_if2 inet from any to any -> ($ext_if2)
#Проброс на SQUID
rdr on $int_if inet proto tcp from any to any port www -> 127.0.0.1 port 3128

block in from any to any
block out from any to any

#Антиспуфинг
antispoof quick for $int_if inet

#  пропускаем все исходящие пакеты на внутреннем интерфейсе
pass out on $int_if from any to $int_net

#проброс без балансировки (согласно списку адресов)
pass in quick on $int_if route-to ($ext_if2 $gw2) from $to_trans to !$int_net keep state
pass in quick on $int_if route-to ($ext_if $gw1) from $to_ertel to !$int_net keep state

#  пропускаем (quick) пакеты предназначенные самому шлюзу
pass in quick on $int_if from $int_net to $int_if
pass in quick on $int_if route-to { ($ext_if $gw1), ($ext_if2 $gw2)} round-robin sticky-address proto tcp from $int_net to any flags S/SA keep state
#Добавил sticky-address по дельному совету RicoX

#  балансировка исходящего icmp и udp трафика идущего из внутренней сети
pass in on $int_if route-to { ($ext_if $gw1), ($ext_if2 $gw2) } round-robin proto { udp, icmp } from $int_net to any keep state

#  основные "выпускаюшие" правила на внешнем интерфейсе
pass out on $ext_if proto tcp from any to any flags S/SA modulate state
pass out on $ext_if proto { udp, icmp } from any to any keep state
pass out on $ext_if2 proto tcp from any to any flags S/SA modulate state
pass out on $ext_if2 proto { udp, icmp } from any to any keep state
#  маршрутизация пакетов идущих с любого IP на $ext_if1 через $ext_gw1 и
#  пакетов идущих  на $ext_if2 через $ext_gw2
pass out on $ext_if route-to ($ext_if2 $gw2) from $ext_if2 to any
pass out on $ext_if2 route-to ($ext_if $gw1) from $ext_if to any

Настройку полос пропускания не производил, так как не было необходимости. Включается достаточно просто и быстро, думаю в дальнейшем можно будет дописать конфиг.

Недостаток данного пути в том, что иногда, когда что-то случается со стороны одного из провайдеров, балансировка нагрузки работать перестает.
Для решения данной проблемы были созданы конфигурационные файлы: pf.conf.ertelecom и pf.conf.trans, которые отличаются от основного файла настроек только тем, что строка:

 #pass in on $int_if route-to { ($ext_if $gw1), ($ext_if2 $gw2) } round-robin proto { udp, icmp } from $int_net to any keep state 

из них выкинута. Вместо нее появляются две строки:

pass out route-to ($ext_if $gw1) from $int_if to any
pass out route-to ($ext_if2 $gw2) from $int_if to any

одна из которых работает, в зависимости от того, какой шлюз по умолчанию установлен.

Скрипты переключения выглядит так:

cat er_conn.sh 
#!/bin/sh

GW1=«192.168.63.25»
if1=«192.168.63.26»
#отключаем файрволл
/sbin/pfctl -d
#и включаем файрволл с правилами провайдера №2
/sbin/pfctl -e -f /etc/pf.conf.dom
#Убиваем шлюз по-умолчанию
/sbin/route del default
#Включаем шлюз по-умолчанию нашего провайдера №2
/sbin/route add default $GW1

Аналогично этому скрипту работают скрипты управления включением провайдера №1 и балансировки нагрузки. Меняется только адрес шлюза и файл правил настройки pf.
В cron подключаем скрипт, который каждые несколько (на выбор) минут проверяет на ответ от шлюза:
ping -S <ip адрес шлюза провайдера> <наш реальный ip-адрес>.
Если ответа нет, автоматически подгружаем правила живого канала.

Что касается настройки SQUID, то там я сделал вообще все примитивно. Собрал squid 3 из портов, не меняя ничего, кроме настройки transparent, то есть прозрачный прокси. Привожу его конфигурационный файл:

cat squid.conf
http_port 127.0.0.1:3128 transparent
icp_port 0
hierarchy_stoplist cgi-bin ?
acl QUERY urlpath_regex cgi-bin ?
no_cache deny QUERY
cache_mem 256 MB
maximum_object_size 8092 KB
maximum_object_size_in_memory 512 KB
cache_dir ufs /bkp/var/squid/cache 2048 64 256
cache_access_log /bkp/var/squid/access.log
cache_log /bkp/var/squid/cache.log
cache_store_log /bkp/var/squid/store.log
cache_mgr root@xxx.ru
cache_effective_user squid
cache_effective_group squid
visible_hostname gate
coredump_dir /bkp/var/squid/cache
pid_filename /var/run/squid/squid.pid
acl our_networks src 10.1.1.0/24
http_access allow our_networks

Вот с этим и работаем уже около двух лет, практически ничего не вылетает, шлюз перезагружается только по прихотям электриков, пользователи забыли, что такое проблемы с доступом интернет.
Трафик считаю спиртом (cnupm), с последующей выгрузкой его логов в базу MySQL.

Небольшой бонус: когда я принял хозяйство, все IP были выставлены вручную для машин. Кроме того, к этим IP (а не именам машин) была привязка некоторых сервисов локальной сети. Поэтому родился следующий скрипт для быстрого трансфера настроек IP в формат DHCP конфигурационного файла сервера, а именно той части, которая присваивает IP по МАК-адресу.
Привожу листинг:

  gate# cat do_dhcp 
#!/bin/sh

arp -an | grep <Ваш локальный сетевой интерфейс> > /usr/arp.txt
#В IFS указан пробел в кавычках
IFS=« »
cat /usr/arp.txt | while read line
do
    set -- $line;
    echo -e «host $1 {
                    hardware ethernet $4;
                    fixed-address $2;
                }n»
done

Вывод происходит на экран терминала, но если в echo добавить после последней кавычки > /usr/dhcp.txt, то вывод будет в файл. Далее произошел copy-paste вывода в соответствующий раздел файла dhcpd.conf. Можно так:

cat dhcpd.txt >> /usr/local/etc/dhcpd.conf

http://habrahabr.ru/post/177767/

Понравилась статья, расскажи о ней друзьям, нажми кнопку!