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

в 8:44, , рубрики: балансировка трафика, маршрутизация, Сетевые технологии, системное администрирование

Недавно обзавёлся задачей по балансировке трафика между несколькими 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 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 для ядер &lt3.6 (в ядрах &gt=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-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 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js