Два провайдера на одном роутере


Неприятно, если у провайдера авария и у вас дома пропал доступ в интернет. Вдвойне неприятно, если в этот момент вы находитесь далеко от дома и вам нужно залезть на домашний компьютер или NAS. В моем случае застраховаться от падения канала мне помогла компания МГТС, заменив устаревшую медь на оптику, с которой я получил ещё один канал интернет, и чтение различных мануалов по dd-wrt. Если с оптикой я помочь не смогу, то готовым решением по dd-wrt я с удовольствием поделюсь.

Решение не универсальное, но, как мне кажется, очень простое и на его основе неискушенные пользователи смогут, если не создать свое, то просто расширить свой кругозор и понимание этой популярной прошивки.

Я использую роутер asus rt-n16 и прошивку от тов. Kong 22000++. Интернет от основного провайдера приходит ко мне по кабелю, все настройки получаются роутером по dhcp.

Фактически, все по умолчанию. Адрес роутера во внутренней сети сменен на 192.168.199.1.

Переключение каналов интернета будет выполняться скриптом, который мы создадим во внутренней памяти роутера JFFS2, соответственно, нужно эту внутреннюю память включить.

После включения JFFS2 память нужно очистить. Для этого выберем «Clean JFFS2» и нажмем применить. Возможно, нужно будет перезагрузить роутер. Наша задача — добиться того, чтобы появилось свободное место в этой памяти.

Теперь зайдем на роутер при помощи telnet. Я использую для этого putty:

putty -telnet 192.168.199.1

Имя пользователя root, даже если для логина в вебинтерфейс dd-wrt вы используете другое имя. Пароль — тот, который вы используете для логина в вебинтерфейс.

Следующий шаг. Мы подключаем резервный кабель в порт LAN1 роутера. В моем случае резервный кабель идет от оборудования GPON, которое поставил МГТС. Это оборудование раздает интернет в своей локальной сети 192.168.100.0/255.255.255.0. Нам нужно будет «оторвать» необходимый порт нашего роутера от остальных портов и назначить ему адрес из сети оборудования GPON, например, 192.168.100.200.

Теперь информация специфичная для конкретной железки, а именно asus rt-n16. Наберем в терминале следующие команды и посмотрим ответ:

root@DD-WRT:~# nvram show | grep vlan.*port
vlan2ports=0 8
vlan0ports=1 2 3 4 5*
vlan1ports=4 3 2 1 8*
size: 29978 bytes (2790 left)
root@DD-WRT:~# nvram show | grep vlan.*hw
vlan2hwname=et0
vlan1hwname=et0
vlan0hwname=et0
size: 29978 bytes (2790 left)

Группы портов объединенны в вэланы. Порт 0 — это WAN порт роутера, порты 1234 — это порты роутера, которые соответствуют (внимание!) портам LAN4,LAN3,LAN2,LAN1 роутера, то есть порт 4 подписан на корпусе роутера LAN1. Порт 8 и порт 8* — это порты процессора, через них мы будем видеть интерфейсы в роутере. Я не хочу вдаваться в подробности, принципиально то, что любой вэлан должен замыкаться на процессор роутера. Давайте «оторвем» 4-ый порт:

root@DD-WRT:~# nvram set vlan0ports="1 2 3 5*"
root@DD-WRT:~# nvram set vlan1ports="3 2 1 8*"

а теперь назначим 4 порт в 3-ий вэлан

root@DD-WRT:~# nvram set vlan3ports="4 8"

для того, чтобы новый вэлан был виден, нужно ввести команду

root@DD-WRT:~# nvram set vlan3hwname=et0

сохраним изменения

root@DD-WRT:~# nvram commit

По аналогии, можно сделать для других роутеров с прошивкой dd-wrt.

Адрес нашему новому интерфейсу, который связан с 4-ым портом, мы будем назначать из скрипта при старте. Поэтому, давайте начнём этот скрипт писать. Но прежде, где он будет храниться. Как учит нас Боконон dd-wrt wiki, всякий скрипт в папке /jffs/etc/config/ с расширением .startup выполняется при запуске системы до того, как файрвол будет сконфигурирован. Назовём наш скрипт vlan3.startup и напишем следующие строки:

#!/bin/sh
WAN2_IFNAME=vlan3
WAN2_IPADDR=192.168.100.200
WAN2_GATEWAY=192.168.100.1
WAN2_NETMASK=255.255.255.0
if [ "$(nvram get wan2_ipaddr)" != "$WAN2_IPADDR" ]; then
   nvram set wan2_ifname=$WAN2_IFNAME
   nvram set wan2_ipaddr=$WAN2_IPADDR
   nvram set wan2_gateway=$WAN2_GATEWAY
   nvram set wan2_netmask=$WAN2_NETMASK
   nvram commit
fi
ifconfig $(nvram get wan2_ifname) up $(nvram get wan2_ipaddr) netmask $(nvram get wan2_netmask)

В этих строках всё просто. Мы завели в nvram 4-ре новые переменные и занесли в них название интерфейса, адрес, маску и шлюз для нашего резервного порта интернет. Каждый раз при старте мы проверяем, что адрес именно тот, который задан в переменной скрипта, это на случай, если потребуется поменять настройки резервного интернета. Далее кофигурируем порт заданными значениями. С текущими переменными это выглядело бы так:
ifconfig vlan3 up 192.168.100.200 netmask 255.255.255.0

Добавим к нашему скрипту бесконечный цикл, в котором и будем переключать интернет канал на резервный и обратно:

INTERVAL=30

while sleep $INTERVAL
do
        WAN1ALIVE=0
        WAN2USING=0
        WAN1GW=`nvram get wan_gateway`

        echo "check"

        if [ "$WAN1GW" != "0.0.0.0" ]; then
                if [ "1" = `ping -c 1 $WAN1GW 2>/dev/null | awk    '/packets received/ {print $4}'` ]; then
                        WAN1ALIVE=1
                        echo "wan1alive"
                fi
        fi

        TARGET=`ip -4 route list 0/0  | awk -v gate="via "$WAN2_GATEWAY '$0 ~ gate {print $3}'`
        if [ ! -z "$TARGET" ]; then
                WAN2USING=1
                echo "wan2using"
        fi

        if [ "$WAN1ALIVE" = "1" ] && [ "$WAN2USING" = "1" ]; then
                /jffs/etc/config/wan1.up
                echo "Changed active WAN port to 1!"
        fi

        if [ "$WAN1ALIVE" = "0" ] && [ "$WAN2USING" = "0" ]; then
                if [ "1" = `ping -c 1 $WAN2_GATEWAY 2>/dev/null | awk '/packets received/ {print $4}'` ]; then
                        /jffs/etc/config/wan2.up
                        echo "Changed active WAN port to 2!"
                fi
        fi

done;

Словами этот длинный кусок кода можно описать так. Каждые n-цать секунд мы проверяем:

  1. Текущий адрес шлюза основного канала. Он хранится в переменной wan_gateway в nvram и назначается автоматически, когда основной шлюз получает параметры по dhcp от основного провайдера. Если этот адрес не равен «0.0.0.0», а «0.0.0.0» он равен тогда, когда адрес не может быть получен, то есть провайдер лежит, мы его пингуем и в случае ответа устанавливаем флаг WAN1ALIVE в единицу, то есть основной канал предполагается рабочим.
  2. Присутствует ли адрес резервного шлюза в таблице роутинга. Если присутствует, то устанавливаем флаг WAN2USING=1, то есть в данный момент используется резервный интернет канал.
  3. Если основной канал работает и используется резервный канал, то переключаемся на основной канал интернет.
  4. Если основной канал не работает и не используется резервный канал, то тестируем доступность шлюза резервного канала и переключаемся на рабочий резерв.

Скрипт включения основного канала wan1.up:

#!/bin/sh
# WAN1
DEV=vlan2
GATEWAY=`nvram get wan_gateway`
DNS1=8.8.8.8
DNS2=8.8.4.4

nvram set wan_dns="$DNS1 $DNS2"
while ip route del default; do :; done
ip route add default via $GATEWAY dev $DEV
echo "nameserver $DNS1" >/tmp/resolv.dnsmasq
echo "nameserver $DNS2" >>/tmp/resolv.dnsmasq
pr="$(ps|awk '/dnsmasq/ {print $1}')"
kill -9 $pr
dnsmasq --conf-file=/tmp/dnsmasq.conf

Здесь все просто. Удалили все шлюзы по умолчанию. Да-да, их может быть несколько: если после перезагрузки роутера был недоступен основной канал, то включается резервный интернет и мы имеем один шлюз по умолчанию. Если затем начинает работать основной канал, dd-wrt добавляет ещё один шлюз по умолчанию. Тут уж ничего не поделать. Не хотелось долго думать и появился «грязный хак»: while ip route del default; do :; done, а именно удалять все шлюзы по умолчанию, пока не случится ошибка из-за того, что шлюзов больше нет. Некрасиво, придумайте, как лучше.

После того, как удалили все шлюзы по умолчанию, добавим новый шлюз, который записан в переменную wan_gateway nvram (его туда записала логика dd-wrt при получения параметров от основного провайдера). Заменим dns на dns гугл, убьем процесс dnsmasq и запустим этот процесс вновь.

Скрипт для включения резервного канала wan2.up устроен таким-же образом, но адрес шлюза берётся из пременной wan2_gateway nvram и используется интерфейс связанный с 4-ым портом роутера.

#!/bin/sh
# WAN2
DEV=vlan3
GATEWAY=`nvram get wan2_gateway`
DNS1=8.8.8.8
DNS2=8.8.4.4

nvram set wan_dns="$DNS1 $DNS2"
while ip route del default; do :; done
ip route add default via $GATEWAY dev $DEV
echo "nameserver $DNS1" >/tmp/resolv.dnsmasq
echo "nameserver $DNS2" >>/tmp/resolv.dnsmasq
pr="$(ps|awk '/dnsmasq/ {print $1}')"
kill -9 $pr
dnsmasq --conf-file=/tmp/dnsmasq.conf

Остался маленький скриптик vlan3.wanup, который выполняется после поднятия WAN, сразу после того, как включится файрволл:

#!/bin/sh
iptables -t nat -A POSTROUTING -o $(nvram get wan2_ifname) -j SNAT --to $(nvram get wan2_ipaddr)

Назначение скриптика — добавить правило для ната через резервный интерфейс.

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

Целиком скрипт выглядит вот так:

Вставляем в окно telnet\ssh следующую простыню и нажимаем «Enter»:

nvram set vlan0ports="1 2 3 5*"
nvram set vlan1ports="3 2 1 8*"
nvram set vlan3ports="4 8"
nvram set vlan3hwname=et0
nvram commit
mkdir -p /jffs/etc/config
cat << 'EOF' > /jffs/etc/config/vlan3.startup
#!/bin/sh
WAN2_IFNAME=vlan3
WAN2_IPADDR=192.168.100.200
WAN2_GATEWAY=192.168.100.1
WAN2_NETMASK=255.255.255.0
if [ "$(nvram get wan2_ipaddr)" != "$WAN2_IPADDR" ]; then
   nvram set wan2_ifname=$WAN2_IFNAME
   nvram set wan2_ipaddr=$WAN2_IPADDR
   nvram set wan2_gateway=$WAN2_GATEWAY
   nvram set wan2_netmask=$WAN2_NETMASK
   nvram commit
fi
ifconfig $(nvram get wan2_ifname) up $(nvram get wan2_ipaddr) netmask $(nvram get wan2_netmask)

INTERVAL=30

while sleep $INTERVAL
do
        WAN1ALIVE=0
        WAN2USING=0
        WAN1GW=`nvram get wan_gateway`

        echo "check"

        if [ "$WAN1GW" != "0.0.0.0" ]; then
                if [ "1" = `ping -c 1 $WAN1GW 2>/dev/null | awk    '/packets received/ {print $4}'` ]; then
                        WAN1ALIVE=1
                        echo "wan1alive"
                fi
        fi

        TARGET=`ip -4 route list 0/0  | awk -v gate="via "$WAN2_GATEWAY '$0 ~ gate {print $3}'`
        if [ ! -z "$TARGET" ]; then
                WAN2USING=1
                echo "wan2using"
        fi

        if [ "$WAN1ALIVE" = "1" ] && [ "$WAN2USING" = "1" ]; then
                /jffs/etc/config/wan1.up
                echo "Changed active WAN port to 1!"
        fi

        if [ "$WAN1ALIVE" = "0" ] && [ "$WAN2USING" = "0" ]; then
                if [ "1" = `ping -c 1 $WAN2_GATEWAY 2>/dev/null | awk '/packets received/ {print $4}'` ]; then
                        /jffs/etc/config/wan2.up
                        echo "Changed active WAN port to 2!"
                fi
        fi

done;
EOF
chmod +x /jffs/etc/config/vlan3.startup
cat << 'EOF' > /jffs/etc/config/vlan3.wanup
#!/bin/sh
iptables -t nat -A POSTROUTING -o $(nvram get wan2_ifname) -j SNAT --to $(nvram get wan2_ipaddr)
EOF
chmod +x /jffs/etc/config/vlan3.wanup
cat << 'EOF' > /jffs/etc/config/wan1.up
#!/bin/sh
# WAN1
DEV=vlan2
GATEWAY=`nvram get wan_gateway`
DNS1=8.8.8.8
DNS2=8.8.4.4

nvram set wan_dns="$DNS1 $DNS2"
#`ip -4 route list 0/0  | awk '/default via/ {print "ip route delete default"}'` | sh
# ip route delete default
while ip route del default; do :; done
ip route add default via $GATEWAY dev $DEV
echo "nameserver $DNS1" >/tmp/resolv.dnsmasq
echo "nameserver $DNS2" >>/tmp/resolv.dnsmasq
pr="$(ps|awk '/dnsmasq/ {print $1}')"
kill -9 $pr
dnsmasq --conf-file=/tmp/dnsmasq.conf
EOF
chmod +x vi /jffs/etc/config/wan1.up
cat << 'EOF' > /jffs/etc/config/wan2.up
#!/bin/sh
# WAN2
DEV=vlan3
GATEWAY=`nvram get wan2_gateway`
DNS1=8.8.8.8
DNS2=8.8.4.4

nvram set wan_dns="$DNS1 $DNS2"
#`ip -4 route list 0/0  | awk '/default via/ {print "ip route delete default"}'`|sh
# ip route delete default
while ip route del default; do :; done
ip route add default via $GATEWAY dev $DEV
echo "nameserver $DNS1" >/tmp/resolv.dnsmasq
echo "nameserver $DNS2" >>/tmp/resolv.dnsmasq
pr="$(ps|awk '/dnsmasq/ {print $1}')"
kill -9 $pr
dnsmasq --conf-file=/tmp/dnsmasq.conf
EOF
chmod +x /jffs/etc/config/wan2.up