Скрипт
"чуть-чуть" слушает трафик на
интерфейсе eth0. Если "полезного"
трафика нет, то поднимает интерфейс
eth1 и "деактивирует" eth0. Если
"полезный" трафик есть, то просто
"деактивирует" eth1 (ничего страшного
не произойдёт, если интерфейс уже
"деактивирован")
Можно запускать
в цикле, можно кроном, например, раз в
минуту.
"чуть-чуть" слушает трафик на
интерфейсе eth0. Если "полезного"
трафика нет, то поднимает интерфейс
eth1 и "деактивирует" eth0. Если
"полезный" трафик есть, то просто
"деактивирует" eth1 (ничего страшного
не произойдёт, если интерфейс уже
"деактивирован")
Можно запускать
в цикле, можно кроном, например, раз в
минуту.
Можно
скрипт изменить:
- не слушать трафик
на интерфейсе, а пинговать с него
- не
поднимать/опускать интерфейсы, а просто
перезаписывать маршрут
#!/bin/bash
/sbin/ifconfig
eth0 up
if
! /usr/sbin/tcpdump -i eth0 -n -c 5 -q |/bin/grep " IP "
then
/sbin/ifconfig
eth1 up
/sbin/ifconfig
eth0 down
else
/sbin/ifconfig eth1 down
fi
#!/bin/bash
#
опрашиваемый узел основного провайдера
HOST=10.1.0.254
#
шлюз к основному провайдеру GW1=10.1.0.1
#
шлюз к резервному провайдеру GW2=10.2.0.1
ping
-q -c 1 ${HOST} > /dev/null 2>&1
if
[ $? -eq 0 ]; then
ping
-q -c 1 ${GW1} > /dev/null 2>&1
if
[ $? -eq 0 ]; then
ip
route change default via ${GW1}
else
ip
route change default via ${GW2}
fi
else
ip
route change default via ${GW2}
fi
Нужен
еще постоянный маршрут до опрашиваемого
узла
ip
route add 10.1.0.254 via 10.1.0.1
Скрипт
загоняется в крон на исполнение каждую
минуту.
#!/usr/bin/perl
-w
use strict;
use LockFile::Simple qw(lock trylock
unlock);
require 'sys/syscall.ph';
#
Устанавливаем путь по умолчанию
$ENV{PATH}
= '/bin:/usr/bin:/sbin:/usr/sbin';
#
Отладка
my $debug=1;
my
$lockmgr = LockFile::Simple->make(-format => '%f',
-stale =>
1, -autoclean => 1, -hold => 0,
-efunc => undef, -wfunc
=> undef);
my
$lock = '/opt/change-route.lock';
sub
quit {
# Помещаем сюда код для корректного
#
прекращения работы
exit(0);
};
#
Отделяемся от родителя
fork() && exit;
#
Отключаемся от терминала
close STDOUT; close
STDERR; close STDIN;
#
Делаем корень текужим каталогом
chdir
'/';
#
Создаем новую сессию и становимся
лидером
# группы процессов, чтоб нас
случайно не прибили
syscall(&SYS_setsid);
#
Блокировка (чтоб не запустилось
одновременно 2 процесса)
exit 1 unless
($lockmgr->trylock($lock));
#
Перехватываем сигналы, для корректного
выхода
$SIG{'INT'} = $SIG{'QUIT'} = $SIG{'TERM'} =
'quit';
$SIG{'HUP'} = 'ignore';
#
Основной канал
my %primaryip = (
iface =>
"eth1",
ip => "7.13.6.228",
netmask =>
"255.255.255.224",
route => "7.13.6.225"
);
#
Резервный канал
my %secondaryip = (
iface =>
"eth2",
ip => "21.2.7.41",
netmask =>
"255.255.255.252",
route => "21.2.7.42"
);
#
Ip для теста
my $testip='86.110.181.76';
#
Локальная сеmь
my $iflocal='192.168.0.0/24';
#
Правила для iptables
# основной канал
my
$primaryrules = " -s $iflocal -o $primaryip{iface} -j SNAT
--to-source $primaryip{ip}";
# резервный канал
my
$secondaryrules = " -s $iflocal -o $secondaryip{iface} -j SNAT
--to-source $secondaryip{ip}";
begin:
#
Определение работающего
канала
open(A,"route|");
my
$activeiface='not';
while ()
{
next if (!($_ =~
m/default\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)/));
$activeiface=$1;
};
close(A);
#
Проверка каналов
my $primarystatus=system("ping
-c 2 -I $primaryip{ip} $primaryip{route} > /dev/null");
my
$secondarystatus=system("ping -c 2 -I $secondaryip{ip}
$secondaryip{route} > /dev/null");
system("route add
-host $testip gw $primaryip{route} metric 30");
my
$primarystatusya=system("ping -c 2 -I $primaryip{ip} $testip >
/dev/null");
system("route del -host $testip gw
$primaryip{route} metric 30");
system("route add -host
$testip gw $secondaryip{route} metric 30");
my
$secondarystatusya=system("ping -c 2 -I $secondaryip{ip} $testip
> /dev/null");
system("route del -host $testip gw
$secondaryip{route} metric 30");
open(LogFile,">>
/opt/change-route.log");
if ($debug){
print LogFile
"\n",`date`;
print LogFile "Активный канал
", $activeiface, "\n";
print LogFile "Основной
канал ",$primaryip{iface},", шлюз ",
$primaryip{route}, " ",
$primarystatus?"Недоступен":"Доступен",
" ",$testip, " " ,
$primarystatusya?"Недоступен":"Доступен",
"\n";
print LogFile "Резервный канал
",$secondaryip{iface},", шлюз ",
$secondaryip{route}, " ",
$secondarystatus?"Недоступен":"Доступен",
" ",$testip, " ",
$secondarystatusya?"Недоступен":"Доступен",
"\n";
};
#
Переключение шлюза
# Если используется
резервный канал, но доступен основной
переключаемся на него
if ( ($activeiface eq
$secondaryip{iface}) and ($primarystatusya == 0)) {
print LogFile
"Переключаю на основной",$primaryip{iface},
"\n";
system("route del default gw
$secondaryip{route}");
system("route add default gw
$primaryip{route}");
system("iptables -t nat -D
POSTROUTING $secondaryrules");
system("iptables -t nat
-A POSTROUTING $primaryrules");
open (MAIL, '| /bin/mail -a
7922ххххххх@sms.ugsm.ru');
MAIL "Subject: ",`hostname`,"service info";
MAIL `date`," Pereklyuchenie na osnovnoi kanal";
close
(MAIL);
};
#
Если используется основной канал, но
он не работает переключаемся на
резервный
if ( ($activeiface eq $primaryip{iface}) and
(!($primarystatusya == 0)) ) {
# Смена маршрута
поумолчанию на резервный, при доступности
второго канала
if ($secondarystatusya == 0) {
LogFile "Переключаю на
резервный",$secondaryip{iface}, "\n";
system("route
del default gw $primaryip{route}");
system("route add
default gw $secondaryip{route}");
system("iptables -t
nat -D POSTROUTING $primaryrules");
system("iptables -t
nat -A POSTROUTING $secondaryrules");
open (MAIL, '|
/bin/mail -a
7922ххххххх@sms.ugsm.ru');
MAIL "Subject: ",`hostname`,"service info";
MAIL `date`," Pereklyuchenie na rezervnyi kanal";
close
(MAIL);
};
};
close(LogFile);
sleep
120;
goto begin;
#!/bin/bash
#################################################################
#######################
Back chanel switcher ####################
######################
by Alexsei
######################
#################################################################
#Variables
PETERSTAR=82.XXX.XXX.XXX
#PETERSTAR gateway
SAMPO=10.XXX.XXX.XXX
#SAMPO gateway
if [[ `/bin/ping -c 3 ${PETERSTAR} |
/bin/grep "64 bytes"` ]]
then
if
[[ `/sbin/route | /bin/grep default | /bin/grep ${PETERSTAR}`
]]
then
echo
"`date '+%d-%m-%Y-%H:%M:%S'`: Default gateway is ${PETERSTAR}"
>>/var/log/chanel.log
else
{
#Switch
chanel to PETERSTAR gateway
/sbin/route
del default
/sbin/route add default gw
${PETERSTAR}
echo "`date
'+%d-%m-%Y-%H:%M:%S'`: Switch chanel to PETERSTAR gateway"
>>/var/log/chanel.log
}
fi
else
{
#Switch
chanel to SAMPO
/sbin/route del
default
/sbin/route add default gw
${SAMPO}
echo "`date
'+%d-%m-%Y-%H:%M:%S'`: Switch chanel to SAMPO gateway"
>>/var/log/chanel.log
}
fi
Сценарий для
переключения маршрутов на резервный
канал, если основной шлюз перестал
отвечать на пинг. Умеет проверять
несколько шлюзов, каждому набору шлюзов
может быть назначено несколько подсетей.
Вызывается через cron. Проверен на FreeBSD и
на Linux c iproute2. Команды проверки и
переключения при желании можно
переопределять в файле настроек.
Fallback
gateways
Fallback-gw is a
little Perl script to be called via cron
that checks availibility
of neighbor routers using ping
and activates backup routing on
ping failure.
It can be used as
stupid replacement for BGP/OSPF in multihomed environment.
Tested platforms
are FreeBSD and Linux/iproute2.
Download
version 2009.07.20
Contact author:
ilya.evseev/gmail.com
Usage: fallback-gw
[--dry] [--verbose] [/path/to/config]
Config example #1:
simple
<destination2gateway inet>
destination 0.0.0.0/0
gateway 1.1.1.1
gateway 2.2.2.2
</destination2gateway>
Config example #2:
complete
<commands>
change_default_route = /usr/local/sbin/change_default_route
</commands>
<gateway first_isp>
gateway 1.1.1.1
</gateway>
<gateway second_isp>
gateway 2.2.2.2
</gateway>
<destination first_nets>
destination 1.1.0.0/16
destination 3.3.3.0/24
</destination>
<destination second_nets>
destination 2.2.0.0/16
destination 4.4.4.0/24
</destination>
<destination2gateway first>
gateway first_isp
destination first_nets
</destination2gateway>
<destination2gateway second>
gateway second_isp
destination second_nets
</destination2gateway>
<destination2gateway inet>
destination 0.0.0.0/0
gateway first_isp
gateway second_isp
</destination2gateway>
Enjoy! :-)
#!/usr/bin/perl
#
# fallback-gw
#
# Checks availibility of neighbor routers using ping
# and activates backup routing on ping failures.
#
# Written by ilya.evseev@gmail.com at Jan-May 2009
# Distributed as Public Domain
#
# http://sources.homelink.ru/fallback-gw/
#
use strict;
use warnings;
use FindBin;
use Config::General;
my $dry_run = (@ARGV and $ARGV[0] eq '--dry' ) ? shift @ARGV : undef;
my $verbose = (@ARGV and $ARGV[0] eq '--verbose') ? shift @ARGV : undef;
my $cfgname = @ARGV ? shift @ARGV : "$FindBin::Bin/$FindBin::Script.conf";
my $cfgfile = new Config::General($cfgname);
my %config = $cfgfile->getall;
my %dest2gateways; # { IP/Mask => [ gw1, gw2, ... ], ... }
my $exitcode = 0;
my $net = new netstuff($config{commands});
sub enumerate_list($$$);
sub enumerate_ref($$$) {
my ($name, $conf, $handler) = @_;
#print "DEBUG: Config $name = $conf\n";
return $handler->($conf)
if $conf =~ /^\d+\.\d+\.\d+\.\d+$/
or $conf =~ /^\d+[\.\d]+\/\d+$/;
#print "DEBUG: Recurse $name = $conf\n";
my $a = $config{$name} or die "No section of $name type!\n";
my $b = $a->{$conf} or die "No section $conf of $name type!\n";
my $c = $b->{$name} or die "No $name lines in $conf section!\n";
enumerate_list($conf, $c, $handler);
}
sub enumerate_list($$$) {
my ($name, $conf, $handler) = @_;
die "Missing config $name!\n" if $name and not $conf;
return unless $conf;
my $typ = ref($conf);
#print "DEBUG: Parse name = $name, type is '$typ'\n";
if ($typ eq '') {
enumerate_ref($name, $conf, $handler);
} elsif ($typ eq 'HASH') {
$handler->($_) while (undef,$_) = each %$conf;
} elsif ($typ eq 'ARRAY') {
$handler->($_) foreach @$conf;
} else {
die "Wrong config type for $name is $typ!\n";
}
}
sub enumerate2array($$;$) {
my ($name, $conf, $handler) = @_;
my @result;
enumerate_list($name, $conf->{$name}, sub {
return $handler->($_[0]) if $handler;
enumerate_ref($name, $_[0], sub {
push @result, $_[0]
} )
} );
\@result;
}
#use Data::Dumper;
#print Dumper(\%config);
enumerate2array('destination2gateway', \%config, sub {
my $d = enumerate2array('destination', $_[0]);
my $g = enumerate2array('gateway', $_[0]);
foreach(@$d) {
my $dd = $dest2gateways{$_} ||= [];
push @$dd, $_ foreach @$g;
}
} );
#print Dumper(\%dest2gateways);
#exit;
my $MAX_NETMASK_LENGTH = $config{max_netmask_length} || 30;
my $Pings_count = $config{pings_count} || 10;
#---------------------------------------------------------------
my %gw_routes;
my %gw_status;
my %gw4route;
sub debug_print { print "[$$] ".localtime()." -- @_" if $verbose; }
sub info_print { print STDERR "[$$] ".localtime()." -- @_"; }
sub check_status($) {
my $gw = shift;
return $gw_status{$gw} if exists $gw_status{$gw};
debug_print "Ping router $gw...\n";
my $rc = $net->ping_neighbor($gw, $Pings_count);
die "Cannot ping $gw: $!\n"
if $rc == -1;
die "Ping $gw died with signal ".($? & 127)."\n"
if $rc & 127;
$gw_status{$gw} = ($rc >> 8);
}
$net->list_routes(\%gw4route);
while(my ($dest, $gw) = each %gw4route) {
debug_print "Route $dest via $gw...\n";
if ($MAX_NETMASK_LENGTH and $dest =~ /^\d+\.\d+\.\d+\.\d+\/(\d+)$/) {
next if $1 > $MAX_NETMASK_LENGTH;
}
my $r = ($gw_routes{$gw} ||= {});
$r->{$dest} = 1;
if (check_status($gw)) {
info_print "Route $dest via $gw: FAILED\n";
} else {
debug_print "Route $dest via $gw: ok\n";
}
}
while (my ($current_gw, $status) = each %gw_status) {
next if !$status; # ..'0' means OK
debug_print "Not responding: ${current_gw}...\n";
my $found;
DEST: while (my ($dest, undef) = each %{$gw_routes{$current_gw}}) {
#debug_print "...handle destination $dest...\n";
my $d2g = $dest2gateways{$dest};
unless ($d2g) {
info_print "...delete route $dest via $current_gw\n";
$net->delete_route($dest, $current_gw)
unless $dry_run;
$exitcode++;
next DEST;
}
foreach my $new_gw (@$d2g) {
next if $current_gw eq $new_gw;
debug_print "...check $new_gw for replace\n";
next if check_status($new_gw); # ..'1' means FAILED, goto next
info_print "...new router for $dest is $new_gw\n";
$net->change_default_route($new_gw)
unless $dry_run;
$exitcode++;
next DEST;
}
my $msg = "Cannot get fallback for $dest instead of $current_gw!\n";
@$d2g > 1 ? info_print($msg) : debug_print($msg);
}
}
while (my ($dest, $gateways) = each %dest2gateways) {
next if exists $gw4route{$dest};
info_print "Restore gateway to $dest\n";
die "No router configured for $dest!\n"
unless $gateways and @$gateways;
my $gw = $$gateways[0];
info_print "...via $gw\n";
$net->add_route($dest, $gw)
unless $dry_run;
$exitcode++;
}
#===============================================================
package core_netstuff;
sub new($) {
my $class = shift;
bless( { gw_keyword => 'gw' }, ref($class) || $class );
}
sub add_route($$$) {
my ($self, $dest, $gw) = @_;
system("route add $dest $self->{gw_keyword} $gw");
}
sub delete_route($$;$) {
my ($self, $dest, $gw) = @_;
system("route delete $dest");
}
sub change_default_route($$) {
my ($self, $new_gw) = @_;
system("route delete default; route add default $self->{gw_keyword} $new_gw")
}
sub ping_custom($$$) {
my ($self, $opts, $dest) = @_;
system("ping $opts $dest >/dev/null 2>&1");
$?;
}
sub ping_neighbor($$;$) {
my ($self, $dest, $count) = @_;
$count ||= 3;
$self->ping_custom("-nq -t1 -w$count -c1", $dest);
}
sub list_routes($$) {
my ($self, $gw4route) = @_;
open F, "netstat -nr |" or die "Cannot run netstat: $!\n";
while(<F>) {
my ($dest, $gw, $destmask, $flags, $mss, $win, $irtt, $iface, $tail) = split;
next if $tail or not $flags or $flags ne 'UG';
next unless $gw =~ /^\d+\.\d+\.\d+\.\d+$/;
$dest = '0.0.0.0/0' if $dest eq 'default';
# !!! TODO: convert $destmask to $destbits, merge to $dest
$gw4route->{$dest} = $gw;
}
close F;
}
#===============================================================
package linux_modern_netstuff;
use base 'core_netstuff';
sub add_route($$$) {
my ($self, $dest, $gw) = @_;
system("ip route add $dest via $gw");
}
sub delete_route($$;$) {
my ($self, $dest, $gw) = @_;
system("ip route delete $dest")
}
sub change_default_route($$) {
my ($self, $new_gw) = @_;
system("ip route delete default; ip route add default via $new_gw")
}
sub list_routes($$) {
my ($self, $gw4route) = @_;
open F, "ip route |" or die "Cannot run \"ip route\": $!\n";
while(<F>) {
next if / scope /;
next unless /^([^\s]+) via (\d+\.\d+\.\d+\.\d+) /;
my ($dest, $gw) = ($1, $2);
$dest = '0.0.0.0/0' if $dest eq 'default';
$gw4route->{$dest} = $gw;
}
close F;
}
#===============================================================
package freebsd_netstuff;
use base 'core_netstuff';
sub new($) {
my $class = shift;
my $self = $class->SUPER::new();
$self = bless($self, ref($class) || $class);
$self->{gw_keyword} = '';
$self;
}
sub ping_neighbor($$;$) {
my ($self, $dest, $count) = @_;
$count ||= 3;
$self->ping_custom("-nq -m1 -c$count -o", $dest);
}
sub list_routes($$) {
my ($self, $gw4route) = @_;
open F, "netstat -nr |" or die "Cannot run netstat: $!\n";
while(<F>) {
my ($dest, $gw, $flags, $tail) = split;
next unless $flags;
next unless $flags eq 'UGS' or $flags eq 'UGHS';
next unless $gw =~ /^\d+\.\d+\.\d+\.\d+$/;
$dest = '0.0.0.0/0' if $dest eq 'default';
$gw4route->{$dest} = $gw;
}
close F;
}
#===============================================================
package netstuff;
#use base 'core_netstuff';
sub new($$;$) {
my ($class, $config, $core) = @_;
my $self = bless({}, ref($class) || $class);
if ($core) {
# ..do nothing
} elsif ($^O eq 'freebsd') {
$core = new freebsd_netstuff();
} elsif (-x '/sbin/ip') {
$core = new linux_modern_netstuff();
} else {
$core = new core_netstuff();
}
$self->{config} = $config;
$self->{core} = $core;
$self;
}
sub exec_cmd($$;@) {
my ($self, $cmdname) = (shift, shift);
return (0,0) unless $self->{config} and my $cmd = $self->{config}->{$cmdname};
#print STDERR "Exec \"$cmd @_\"\n";
system("$cmd @_ >/dev/null");
return (1, $?);
}
sub add_route($$$) {
my ($self, $dest, $gw) = @_;
$self->{core}->add_route($dest, $gw);
}
sub delete_route($$;$) {
my ($self, $dest, $gw) = @_;
$self->{core}->delete_route($dest, $gw);
}
sub change_default_route($$) {
my ($self, $new_gw) = @_;
my ($a, $b) = $self->exec_cmd("change_default_route", $new_gw);
return $b if $a;
$self->{core}->change_default_route($new_gw);
}
sub list_routes($$) {
my ($self, $gw4route) = @_;
$self->{core}->list_routes($gw4route);
}
sub ping_neighbor($$;$) {
my ($self, $dest, $count) = @_;
my ($a, $b) = $self->exec_cmd("ping", $dest);
return $b if $a;
#print STDERR "Core ping\n";
$self->{core}->ping_neighbor($dest, $count);
}
## EOF ##
Имеется сервер ASPLinux 10, с установленным postfix и 3-я сетевыми интерфейсами. Задача: корректная работа почты через один из интерфейсов, при этом в качестве default gw указан другой. Реализация: 1) в /etc/iproute2/rt_tables добавляем: 201 T1 202 T2 2) создаём скрипт и даём права на запуск, предварительно исправив нужные параметры IP - адреса сетевых интерфейсов P - адреса шлюзов #!/bin/sh IP1=192.168.4.1 IP2=217.1.1.2 P1=192.168.4.2 P2=217.1.1.1 ip route add default via $P1 table T1 ip route add default via $P2 table T2 ip route add default via $P1 ip rule add from $IP1 table T1 ip rule add from $IP2 table T2 В результате получим, что если пакет пришёл на P2, то он не пойдёт через default gw P1, а уйдёт через тот же интерфейс с IP1 | |