Недавно обзавёлся задачей по балансировке трафика между несколькими usb-модемами. В итоге родилось решение коим и хочу поделиться с читателим.
На момент написания статьи это 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 не нашёл. Киньте в меня ссылкой если они есть. Обычно публикуют свои собственные велосипеды на базе ip route, вот и я туда же.
Есть парочка OpenWrt-специфичных:
Основной трафик будет адресован сервису на 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
- Применить изменения:
sysctl -p
- Если какие-либо интерфейсы находятся в одной подсети:
- Настроить интерфейсы:
- Если настройка происходит локально, рекомендую положить все интерфейсы во избежание проблем с поднятием:
service networking stop
ну или
ifdown eth1 eth2 ... ethN
- Адаптировать примеры из interfaces.d/* под себя.
пример для eth0
auto 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-tunup "/etc/network/balancing_restart tun0 start"
down "/etc/network/balancing_restart tun0 stop";ca /etc/openvpn/ca.crt
CA CERT HERE;cert /etc/openvpn/ovpn-client-example.crt
CERT HERE;key /etc/openvpn/ovpn-client-example.key
PRIVATE KEY HERE;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 inet6 ::1/128 scope host 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 inet6 fe80::182b:3aff:fe4b:5e54/64 scope link 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 inet 192.168.1.12/24 brd 192.168.1.255 scope global secondary eth1 valid_lft forever preferred_lft forever inet6 fe80::5a2c:80fb:fe13:9263/64 scope link 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 19 1140 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 590 92460 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;
Комментарии/предложения/критика горячо приветствуются!
Автор: vmspike