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

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