Для реализации функции автоматического переключения(отказоустойчивости) необходимо выполнить две задачи:

1. Протестировать состояние каналов и выбрать приоритетный
2. Произвести переключение

Первый пункт я давным-давно реализовал простеньким perl скриптом. Он прошел долгий путь, мутировал и изменялся, поэтому вид имеет кривоватый :)
Для публикации здесь я его чуток причесал, но глобальных изменений не вносил.

Для работы скрипта необходим модуль Fcntl, установить его через CPAN можно так:

cpan install Fcntl

или так:

perl -MCPAN -e shell

и ввести install Fcntl

Скрипт check_chanel.pl (скачать):

#!/usr/bin/perl
 
###################################################################
#Name: check_chanel.pl
#Version: 1.0.4
#Created: Andrey Orlov
#Email: tangarus(a)gmail.com
#Web: http://www.tangarus.ru/
#Date: 02.2010
#Description:Автоматическое переключение каналов на linux роутере
####################################################################
 
use Fcntl qw(:DEFAULT :flock O_RDWR O_CREAT);
 
#Проверяем что запущен только один экземпляр скрипта
test_unique();
 
my @prov_name, @prov_if, @prov_gw, @prov_exec, @ping_res, @route_res, $all_work_exec, $num_prov, @hosts_to_ping, $ping_res_local;
 
#Что пингуем(рекомендую IP-адрес)
#Помним, что чем больше хостов пингуем - тем больше на это надо времени
@hosts_to_ping = ('213.180.204.8', '93.158.134.8'); #это разные IP-адреса www.ya.ru
 
#Сколько каналов(провайдеров)
$num_prov = 3;
 
#Приоритет провайдеров
@prio = (2,3,1);
 
#Команда выполняемая, если все провайдеры работают(пусто - не использовать)
$all_work_exec = '/opt/switch_to_balanced';
 
#Первый провайдер
$prov_name[1] = 'Inet'; #название провайдера
$prov_if[1] = 'eth1'; #интерфейс
$prov_gw[1] = '212.152.X.1'; #IP-адрес шлюза провайдера
$prov_exec[1] = '/opt/switch_to_inet'; #команда для переключения на этого провайдера
 
#Второй провайдер
$prov_name[2] = 'RialKom';
$prov_if[2] = 'eth1';
$prov_gw[2] = '80.X.255.129';
$prov_exec[2] = '/opt/switch_to_rialcom';
 
#Третий провайдер
$prov_name[3] = 'Gldn';
$prov_if[3] = 'eth3';
$prov_gw[3] = '62.X.7.241';
$prov_exec[3] = '/opt/switch_to_gldn';
 
#Пингуем всех
for($i=1;$i<=$num_prov;$i++){
print "ping $prov_name[$i] .... \n";
$ping_res[$i] = 1;
foreach $host_to_ping (@hosts_to_ping){
#Меняем маршрут
`/sbin/ip route replace $host_to_ping via $prov_gw[$i] dev $prov_if[$i]`;
#Чистим кеш, если надо
#`«/sbin/ip route flush cache`
$ping_res_local = `ping $host_to_ping -I $prov_if[$i] -c 5|grep \"100% packet loss\" -c`;
chomp($ping_res_local);
if ($ping_res_local ne 1){$ping_res[$i] = 0;};
};
};
 
#Проверяем через кого сейчас работаем
for($i=1;$i<=$num_prov;$i++){
$route_res[$i] = `/sbin/ip route|grep default|awk '{ print $3 }'|grep $prov_gw[$i] -c`;
chomp($route_res[$i]);
};
 
 
#Выводим результаты
print "\n\nResults:\n";
print "********* \n";
for($i=1;$i<=$num_prov;$i++){
print "Provider: $prov_name[$i] \n";
print "channel is down: $ping_res[$i] \n";
print "channel is current: $route_res[$i] \n";
print "********* \n";
};
 
$working_found = 0;
$all_work = 1;
foreach $prov (@prio){
if ($ping_res[$prov] eq 1){$all_work = 0;};
};
 
if (($all_work eq 1)&&($all_work_exec ne '')){
print "All providers work fine, switch to balanced mode\n";
system($all_work_exec);
$working_found = 1;
}else{
foreach $prov (@prio){
if ($ping_res[$prov] ne 1)
{
if ($route_res[$prov] ne 1)
{
print "Switch to $prov_name[$prov]\n";
system($prov_exec[$prov]);
$working_found = 1;
last;
}else{
print "Work trought $prov_name[$prov]\n";
$working_found = 1;
last;
};
};
};
};
 
if ($working_found eq 0)
{
print "Cannot found working channel!!! Nichego ne rabotaet!!! Ales kaput!!!\n";
};
 
exit();
 
sub test_unique(){
my @a=split /\//,$0;
my $lockfile="/var/run/".$a[$#a]."\.lock";
if (-e $lockfile){
if (sysopen(FH, $lockfile, O_WRONLY) && flock(FH, LOCK_EX|LOCK_NB)){
return 0;
} else {
print "Script $a[$#a] alredy started!!!\n"; exit(); }
}
sysopen(FH, $lockfile, O_WRONLY|O_CREAT) && flock(FH, LOCK_EX|LOCK_NB) || die $!;
return 0;
};

Сохраняем его как /opt/check_chanel.pl и добавляем права на запуск:

chmod ugo+x /opt/check_chanel.pl

Пример работы, основной канал упал, переключились на 2-ой:

ping Inet ....
ping RialKom ....
ping Gldn ....
 
Results (1=Yes, 0=No):
*********
Provider: Inet
channel is down: 0
channel is current: 0
*********
Provider: RialKom
channel is down: 1
channel is current: 1
*********
Provider: Gldn
channel is down: 0
channel is current: 0
*********
Switch to Inet

основной канал поднялся, переключаемся на него:

ping Inet ....
ping RialKom ....
ping Gldn ....
 
Results (1=Yes, 0=No):
*********
Provider: Inet
channel is down: 0
channel is current: 1
*********
Provider: RialKom
channel is down: 0
channel is current: 0
*********
Provider: Gldn
channel is down: 0
channel is current: 0
*********
Switch to RialKom

нормальный цикл работы на основном канале:

ping Inet ....
ping RialKom ....
ping Gldn ....
 
Results (1=Yes, 0=No):
*********
Provider: Inet
channel is down: 0
channel is current: 0
*********
Provider: RialKom
channel is down: 0
channel is current: 1
*********
Provider: Gldn
channel is down: 0
channel is current: 0
*********
Work trought RialKom

Приведу пример файла переключающего канал:

#!/bin/bash
 
/sbin/ip route delete default table main
/sbin/ip route delete default table T3
/sbin/ip route add default via 80.X.255.129 table main
/sbin/ip route add default via 80.X.255.129 table T3
/sbin/ip route flush cache
 
/sbin/iptables-restore /etc/iptables.nat

Вообще я планировал сначала описать создание multihomed роутера, а потом уже этот пост, но товарисчу Teacher'у срочно понадобилась инфа по автоматическому переключению. Так что скоро будет и указаный мануал.

Ну и напоследок, если все устраивает, добавляем в cron проверку каналов каждые 2 минуты:

*/2 * * * * /opt/check_chanel.pl