Балансування трафіку між декількома [vpn-]інтерфейсами в одній підмережі

Нещодавно обзавівся завданням балансування трафіку між декількома 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;
  • Моніторинг стану підключення. Є два типи:
    1. multi: connection-based балансування;
    2. solo: redundancy mode, балансування не використовується, а використовується тільки живий інтерфейс з максимальним пріоритетом.

    Жвавість підключення визначається мінімальним обсягом трафіку за період між перевірками, і якщо він менше порогового значення, то за допомогою пінгу зовнішнього хоста.
Крім цього, є готові інструкції як отримати доступ до веб інтерфейсу кожного з модемів (у них же у всіх однакові IP): можна прив'язати браузер до конкретного інтерфейсу.

Як це працює?
При піднятті або опусканні інтерфейсу, який бере участь у балансуванні, автоматично повинен рестартовать скрипт balancing (з допомогою balancing_restart). Це забезпечується відповідною настроюванням інтерфейсів.

Скрипт ініціалізує всі згадані у налаштуваннях інтерфейси (і в той же час доступні) для балансування: додає відповідні правила маршрутизації (ip rule), маршрути (ip route), налаштовує firewall (iptables).

В залежності від налаштувань режиму (solo або multi, які, до речі, можна міняти " на льоту записавши відповідне слово в balancing_mode), буде проводитися балансування між інтерфейсами з вказаними вагою (multi), або використовуватися живий інтерфейс з максимальним пріоритетом (solo).

Через певний період часу скрипт перевіряє всі інтерфейси на жвавість (а також змінився режим), і включає/відключає їх відповідно допомогою редагування таблиць маршрутизації.

Приблизні вимоги
  • Ubuntu Вірного 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.. = 2
      


    • Прибрати routing cache для ядер <3.6 (в ядрах >=3.6 routing cache для ipv4 вже не використовується):

      # Remove routing cache if exists
      net.ipv4.route.max_size = 0
      

    • Застосувати зміни:

      sysctl -p
      
  • Налаштувати інтерфейси:

    • Якщо настройка відбувається локально, рекомендую покласти всі інтерфейси щоб уникнути проблем з підняттям:

      networking service 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-серверів імен 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 add rule priority 10 from 192.168.1.10 lookup eth0
      up ip add rule priority 110 from all oif eth0 lookup eth0
      down while ip delete rule 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="ні"
      

    • Приклади конфігурації клієнтів є в openvpn/*:

      приклад клієнтського конфига tun0 поверх eth0# OpenVPN-client config for example 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 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>

  • Відредагувати змінні під себе в 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 local lookup
    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 main lookup
    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 out in 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 out in source destination
    
    Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
    pkts bytes target prot opt out in source destination
    
    Chain OUTPUT (policy ACCEPT 3344 packets, 541K bytes)
    pkts bytes target prot opt out in source destination
    
    Chain POSTROUTING (policy ACCEPT 3344 packets, 541K bytes)
    pkts bytes target prot opt out in 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;
Коментарі/пропозиції/критика гаряче вітаються!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.