На момент написания статьи это balancing_v0.5.2-alpha.
Изначально задача формулировалась примерно так:
Есть пучёк armhf девайсов c Ubuntu Trusty на борту.
У них есть несколько подключений к интернету. Обычно это основное проводное подключение (eth0) и несколько HiLink usb-модемов Huawei E303 (eth1-eth5). Через каждое из этих подключений нужно поднять openvpn-клиентов к единственному серверу и через них уже балансировать трафик.
Всё бы ничего, но у этих модемов нет возможности изменения подсети и шлюза (гвоздями прибиты 192.168.1.1/24), причём прошивок с реализацией этой возможности тоже не нашлось (в отличии, например от E3272 для которого есть прошивки с таким функционалом). Кроме того даже если бы и нашлись, то vpn-подключения всё равно были бы в одной подсети и с одинаковым шлюзом. Т.е. без продвинутой маршрутизации (policy routing) не обойтись.
Ах, да, ещё надо мониторить каждое подключение и отключать/включать, если порвалось/возобновилось. Т.е. маршрутизацией нужно управлять динамически.
Готовых решений под «обычный» Linux не нашёл. Киньте в меня ссылкой если они есть. Обычно публикуют свои собственные велосипеды на базе iproute2, вот и я туда же.
Есть парочка OpenWrt-специфичных:
В комментариях подкинули ещё решений:
- MPTCP: Только TCP, требует патченое ядро на обоих концах, максимально плавные переключения между интерфейсами;
- MULTI Network Manager;
Основной трафик будет адресован сервису на openvpn-сервере, и для него достаточно будет балансировать соединения. Имейте ввиду, что такой способ балансировки не очень хорошо подходит для веб-сёрфинга, т.к. некоторые соединения внутри https-сессии могут быть направлены в разные интерфейсы. Тут нужно балансировать сессии (несколько соединений кряду, flow-based), поправьте меня, если не прав. В планах реализовать этот способ, вкупе с per-packet- и hash-based.
Фичи этой реализации
- Может использоваться для балансировки трафика на интерфейсах в одной подсети с одинаковым IP шлюза. Это полезно не только для usb-модемов, но и для других подключений, доступа к перенастройке которых у тебя нет, или перенастройка не желательна;
- Поддержка балансировки поверх vpn;
- Мониторинг состояния подключения. Есть два типа:
- multi: connection-based балансировка;
- solo: redundancy mode, балансировка не используется, а используется только живой интерфейс с максимальным приоритетом.
Кроме этого, есть готовые инструкции как получить доступ в веб интерфейсу каждого из модемов (у них же у всех одинаковые IP): можно привязать браузер к конкретному интерфейсу.
Как это работает?
При поднятии или опускании интерфейса, который участвует в балансировке, автоматически должен рестартовать скрипт balancing (с помощью balancing_restart). Это обеспечивается соответствующей настройкой интерфейсов.
Скрипт инициализирует все упомянутые в настройках интерфейсы (и в то же время доступные) для балансировки: добавляет соответствующие правила маршрутизации (ip rule), маршруты (ip route), настраивает firewall (iptables).
В зависимости от настроек режима (solo или multi, которые, кстати, можно менять на лету записав соответствующее слово в balancing_mode), будет либо производиться балансировка между интерфейсами с указанным весом (multi), либо использоваться живой интерфейс с максимальным приоритетом (solo).
Через определённый период времени скрипт проверяет все интерфейсы на живость (а также сменился ли режим), и включает/отключает их соответственно посредством редактирования таблиц маршрутизации.
Примерные требования
- Ubuntu Trusty 14.04 LTS (другие linux также могут быть использованы, но все настройки и тесты проводились именно в этой ОС)
- Ядро Linux с поддержкой CONFIG_IP_ROUTE_MULTIPATH, CONFIG_IP_MULTIPLE_TABLES, CONFIG_IP_ADVANCED_ROUTER:
for conf in /proc/config.gz /boot/config-$(uname -r) /boot/config; do zgrep -e CONFIG_IP_ROUTE_MULTIPATH -e CONFIG_IP_MULTIPLE_TABLES -e CONFIG_IP_ADVANCED_ROUTER $conf 2>/dev/null; done
- Пакеты:
apt-get install iproute2 iptables coreutils iputils-ping grep sed
Настройка
- Скопировать файлы в /etc/network:
mkdir temp && cd temp git clone https://github.com/vmspike/balancing cd balancing chmod +x balancing/balancing{_restart,} add_rt_table get_ovpn_by_base_ip bandwidth-measure cp balancing/balancing* get_ovpn_by_base_ip add_rt_table bandwidth-measure /etc/network/ cd ../../ && rm -rf temp
- Отключить или снести к чертям свинячим NetworkManager, если наличествует, а то всё испортит:
apt-get install usb-modeswitch # Нужно для большинства usb-модемов apt-get purge network-manager network-manager-gnome apt-get autoremove # Аккуратно с этим, проверь точно ли всё это тебе не нужно.
- Настроить параметры ядра в /etc/sysctl.conf:
- Если какие-либо интерфейсы находятся в одной подсети:
# ARP kernel settings for multiple interfaces in the same subnet net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.all.arp_filter = 1 net.ipv4.conf.all.arp_announce = 1 # Enable Loose Reverse Path net.ipv4.conf.all.rp_filter = 2
- Убрать routing cache для ядер <3.6 (в ядрах >=3.6 routing cache для ipv4 уже не используется):
# Remove routing cache if exists net.ipv4.route.max_size = 0
- Отключить IPv6, если нигде больше не используется, для балансировки используется только IPv4:
# Disable IPv6 net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 net.ipv6.conf.lo.disable_ipv6 = 1
- Применить изменения:
sysctl -p
- Если какие-либо интерфейсы находятся в одной подсети:
- Настроить интерфейсы:
- Если настройка происходит локально, рекомендую положить все интерфейсы во избежание проблем с поднятием:
ну илиservice networking stop
ifdown eth1 eth2 ... ethN
- Адаптировать примеры из interfaces.d/* под себя.
пример для eth0auto eth0 allow-hotplug eth0 iface eth0 inet static address 192.168.1.10 network 255.255.255.0 dns-nameservers 8.8.8.8 208.67.222.222 pre-up /etc/network/add_rt_table eth0 # Gateway setup up ip route add default via 192.168.1.1 dev eth0 src 192.168.1.10 proto static table eth0 up ip route add default via 192.168.1.1 dev eth0 src 192.168.1.10 proto static table default metric 2000 # IP rules setup for separate routing table up ip rule add priority 10 from 192.168.1.10 lookup eth0 up ip rule add priority 110 from all oif eth0 lookup eth0 down while ip rule delete lookup eth0; do :; done || exit 0 # Start/stop OpenVPN up service openvpn start $(/etc/network/get_ovpn_by_base_ip 192.168.1.10) || exit 0 down service openvpn stop $(/etc/network/get_ovpn_by_base_ip 192.168.1.10) || exit 0 # Restart balancing up /etc/network/balancing_restart down /etc/network/balancing_restart # If it's WiFi interface #wpa-driver nl80211 #wpa-key-mgmt WPA-PSK #wpa-proto WPA2 #wpa-ssid SSID #wpa-psk PASSWORD
- Если настройка происходит локально, рекомендую положить все интерфейсы во избежание проблем с поднятием:
- Настроить OpenVPN клиентов, если используются:
- Установить OpenVPN:
## Repo for amd64 and i386 arch # wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg|apt-key add - # echo "deb http://swupdate.openvpn.net/apt trusty main" > /etc/apt/sources.list.d/swupdate.openvpn.net.list apt-get update apt-get install openvpn
- Настроить OS:
adduser --system --no-create-home --home /nonexistent --disabled-login --group openvpn mkdir /var/log/openvpn chown openvpn:openvpn /var/log/openvpn
- Отключить автозапуск всех клиентов при старте. В /etc/default/openvpn раскомментировать строку:
AUTOSTART="none"
- Примеры конфигурации клиентов есть в openvpn/*:
пример клиентского конфига tun0 поверх eth0# OpenVPN-client config example for balancing client remote openvpn.example.com 1194 local 192.168.1.10 # bind to eth0 ;nobind dev tun0 proto udp resolv-retry infinite remote-cert-tls server comp-lzo log-append /var/log/openvpn/ovpn-client-example.log verb 3 ;daemon # Commented because balancing_restart require root permissions ;user openvpn ;group openvpn ;persist-key ;persist-tun up "/etc/network/balancing_restart tun0 start" down "/etc/network/balancing_restart tun0 stop" ;ca /etc/openvpn/ca.crt <ca> CA CERT HERE </ca> ;cert /etc/openvpn/ovpn-client-example.crt <cert> CERT HERE </cert> ;key /etc/openvpn/ovpn-client-example.key <key> PRIVATE KEY HERE </key> ;tls-auth /etc/openvpn/ta.key 1 key-direction 1 <tls-auth> STATIC TLS KEY HERE </tls-auth>
- Установить OpenVPN:
- Отредактировать переменные под себя в balancing_vars (см. комментарии к файлу)
- Поднять интерфейсы для балансировки (/etc/network/balancing_restart должен запускаться при поднятии/опускании интерфейса):
- Проверить /var/log/balancing.log на наличие ошибок:
tail -f -n30 /var/log/balancing.log
- Техническую информацию о текущем состоянии балансировки можно посмотреть с помощью такого вот однострочника:
echo ======Current state======; echo ===Addresses===; ip a; echo; echo ===Rules===; ip rule; echo; echo ===Routing tables===; for TBL in $(ip rule | rev | cut -d' ' -f2 | rev | sort -u); do echo ==$TBL==; ip r l t $TBL; echo; done; echo; echo ===IPTABLES===; for table in raw mangle nat filter; do echo ==$table==; iptables -vnL -t $table; echo; done; echo; echo ===MODE===; cat /etc/network/balancing_mode;
пример состояния для пары интерфейсов======Current state====== ===Addresses=== 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 1a:2b:3c:4d:5e:6c brd ff:ff:ff:ff:ff:ff inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0 valid_lft forever preferred_lft forever 7: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 1000 link/ether 12:34:56:78:90:12 brd ff:ff:ff:ff:ff:ff inet 192.168.1.11/24 brd 192.168.1.255 scope global eth1 valid_lft forever preferred_lft forever 8: tun1: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100 link/none inet 172.22.0.3/16 brd 172.22.255.255 scope global tun1 valid_lft forever preferred_lft forever 9: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100 link/none inet 172.22.0.2/16 brd 172.22.255.255 scope global tun0 valid_lft forever preferred_lft forever ===Rules=== 0: from all lookup local 10: from 192.168.1.10 lookup eth0 11: from 192.168.1.11 lookup eth1 110: from all oif eth0 lookup eth0 111: from all oif eth1 lookup eth1 1000: from all fwmark 0x6a lookup tun0 1001: from 172.22.0.2 lookup tun0 1002: from all oif tun0 lookup tun0 1003: from all fwmark 0x6b lookup tun1 1004: from 172.22.0.3 lookup tun1 1005: from all oif tun1 lookup tun1 20000: from all lookup main 30000: from all lookup balancing 32767: from all lookup default ===Routing tables=== ==balancing== default proto static metric 1 nexthop via 172.22.0.1 dev tun0 weight 18 nexthop via 172.22.0.1 dev tun1 weight 1 default via 172.22.0.1 dev tun0 proto static src 172.22.0.2 metric 2 default via 172.22.0.1 dev tun1 proto static src 172.22.0.3 metric 4 default via 172.22.0.1 dev tun0 proto static src 172.22.0.2 metric 1002 default via 172.22.0.1 dev tun1 proto static src 172.22.0.3 metric 1004 ==default== default via 192.168.1.1 dev eth0 src 192.168.1.10 metric 2000 default via 192.168.1.1 dev eth1 src 192.168.1.11 metric 2001 ==eth0== default via 192.168.1.1 dev eth0 src 192.168.1.10 ==eth1== default via 192.168.1.1 dev eth1 src 192.168.1.11 ==local== broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1 local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1 broadcast 172.22.0.0 dev tun1 proto kernel scope link src 172.22.0.3 broadcast 172.22.0.0 dev tun0 proto kernel scope link src 172.22.0.2 local 172.22.0.2 dev tun0 proto kernel scope host src 172.22.0.2 local 172.22.0.3 dev tun1 proto kernel scope host src 172.22.0.3 broadcast 172.22.255.255 dev tun1 proto kernel scope link src 172.22.0.3 broadcast 172.22.255.255 dev tun0 proto kernel scope link src 172.22.0.2 broadcast 192.168.1.0 dev eth0 proto kernel scope link src 192.168.1.10 broadcast 192.168.1.0 dev eth1 proto kernel scope link src 192.168.1.11 local 192.168.1.10 dev eth0 proto kernel scope host src 192.168.1.10 local 192.168.1.11 dev eth1 proto kernel scope host src 192.168.1.11 broadcast 192.168.1.255 dev eth0 proto kernel scope link src 192.168.1.10 broadcast 192.168.1.255 dev eth1 proto kernel scope link src 192.168.1.11 ==main== 169.254.0.0/16 dev eth0 scope link metric 1000 172.22.0.0/16 dev tun1 proto kernel scope link src 172.22.0.3 172.22.0.0/16 dev tun0 proto kernel scope link src 172.22.0.2 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10 192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.11 ==tun0== default via 172.22.0.1 dev tun0 proto static src 172.22.0.2 metric 2 ==tun1== default via 172.22.0.1 dev tun1 proto static src 172.22.0.3 metric 4 ===IPTABLES=== ==mangle== Chain PREROUTING (policy ACCEPT 5473 packets, 631K bytes) pkts bytes target prot opt in out source destination 5473 631K CONNMARK all -- * * 0.0.0.0/0 0.0.0.0/0 CONNMARK restore 19 1140 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 ctorigdst 172.22.0.2 mark match 0x0 MARK set 0x6a 7 420 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 ctorigdst 172.22.0.3 mark match 0x0 MARK set 0x6b Chain INPUT (policy ACCEPT 4621 packets, 587K bytes) pkts bytes target prot opt in out source destination Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 3344 packets, 541K bytes) pkts bytes target prot opt in out source destination Chain POSTROUTING (policy ACCEPT 3344 packets, 541K bytes) pkts bytes target prot opt in out source destination 590 92460 MARK all -- * tun0 0.0.0.0/0 0.0.0.0/0 mark match 0x0 MARK set 0x6a 40 6268 MARK all -- * tun1 0.0.0.0/0 0.0.0.0/0 mark match 0x0 MARK set 0x6b 3344 541K CONNMARK all -- * * 0.0.0.0/0 0.0.0.0/0 CONNMARK save ===MODE=== multi
Известные «особенности»
- Если один из балансируемых интерфейсов лёг/исчез, а скрипт не перезапустился (с помощью balancing_restart), то скрипт падает;
- OpenVPN клиент должен быть запущен под root, чтобы позволять запускать balancing_restart от root;
- В момент перезапуска скрипта пакеты из установленных соединений могут пропасть, т.к. не будет существовать подходящих маршрутов в таблицах маршрутизации. Т.е. поднятие/опускание одного из балансируемых интерфейсов влияет на соединения в других, что не есть гуд. Возможно в дальнейшем это будет исправлено или хотя бы максимально минимизировано;
- При vpn-балансировке если на момент инициализации не удалось поднять vpn-интерфейс (кончился трафик, или не было сигнала, ...) на каком-то из базовых интерфейсов, он пропускается, и если в дальнейшем такая возможность появляется, vpn-интерфейс не будет поднят автоматически.
Планы на будущее
(если проект будет развиваться)- Позволить выбирать использовать ли для балансировки дефолтный маршрут или только определённую подсеть/IP (сейчас используется только маршрут по-умолчанию);
- Добавить маршруты scope link дабы была возможность общаться с хостами своей подсети минуя шлюз;
- Уточнить вычисления ширины канала (сейчас считается будто все проверки в скрипте мгновенные, а они секунды могут отъедать);
- Добавить типы балансировки: flow/session-based, hash-based (require kernels >=4.4 or patch), packet-based;
Комментарии/предложения/критика горячо приветствуются!
Комментарии (5)
phantasm1c
13.06.2016 08:18А почему нельзя поднять несколько туннелей к одному серверу, каждый туннель на отдельном порту сервера, со стороны клиента сделать 3 дополнительных таблицы маршрутизации с дефолтроутом через нужного аплинка, трафик к каждому dst addr + dst port openvpn сервера заворачивать в нужную таблицу маршрутизации через ip rule. Поверх туннелей поднять OSPF и анонсировать сервером дефолтроут или нужную сеть.
В таком случае не нужно будет никаких скриптов, все будет переключаться автоматически с помощью динамической маршрутизации хоть через несколько секунд.
vmspike
13.06.2016 10:44Скрипт нужен по большей части для начальной автоматической конфигурации всех интерфейсов (и vpn, и базовых, в зависимости от режима), а затем лишь для проверки состояния туннеля/интерфейса, вся балансировка осуществляется ядром. См. «пример состояния для пары интерфейсов» под спойлером. Это похоже на то, что ты описал, только каждый туннель запускать к отдельному порту сервера не требуется, тем более, что доступа к конфигурации сервера может и не быть.
Состояние всех интерфейсов обновляется каждые TIME_GAP секунд (60 по-умолчанию, меньше 30 лучше не ставить, особенное если много интерфейсов — проверки могут не уложиться в это время). Хочешь сказать, что с помощью OSPF можно организовать проверку туннеля гораздо быстрее и качественнее чем данными о трафике и пингом? Не использовал пока OSPF нигде.
Ещё фишка в том, что туннель/интерфейс формально может быть жив, но внешний сервис через него не доступен, если трафик на нём меньше порогового, скрипт вырубит такой интерфейс при проверке (уберёт большинство его маршрутов) до того момента, пока связь не восстановится.
Да, сейчас проверки интерфейса на живость происходят последовательно, хорошо было бы их сделать параллельными, чтобы сократить время на проверку, тогда можно чаще проверять (хотя не факт, что это будет лучше, особенно при не стабильном соединении).phantasm1c
13.06.2016 23:23Конечно, это одна из задач OSPF — отслеживание изменения топологии сети. Интервал — зависит от того какой сконфигурируете.
У меня так бегала по двум нестабильным каналам IP-телефония и RDP-сессии к датацентру: если канал падает, трафик перетекает на резервный через порядка 0.8 секунды. При этом не страдают ни TCP-сесии (не разрываются), ни звонки (крякнет легонько, и дальше пойдет).
Проблема «интерфейс вроде жив, но трафик не идёт» в OSPF решается непрерывшим обменом Hello-сообщениями между OSPF-роутерами. Только роутер не получает hello-шек больше чем dead timer — канал считается мёртвым.
В OSPF проверки всех каналов идут параллельно друг другу. Это чуть ли не негласный стандарт interior роутинга, конечно же там всё это предусмотрено :)
Если Вам интересно, вот ключевые слова для того чтобы разобраться: linux advanced routing, ospf, настройка OSPF в quagga (статья для cisco, но квагга копирует их синтаксис. Мне лично для динамической маршрутизации нравится bird).
ValdikSS
Есть, правда, иное решение проблемы: Multipath TCP, с использованием их собственных скриптов или демона MULTI, но такой подход требует использования патченного ядра на обоих концах, и работает только для TCP.
vmspike
Спасибо, добавил в статью и README.
На MPTCP натыкался, но оттолкнуло, что только TCP поддерживается, и ядро нужно специальное. Хотя так-то интересное решение (судя по ролику на главной странице), если позволяет очень плавно переключаться между интерфейсами, что пользователю это даже не заметно.