Switching between Internet Providers on Debian 7
The latest Linux distributions have quite a few goodies in the / etc folder, but few use them competently. Here I will talk about some of them in relation to the latest stable version of Debian, and give an example of the implementation of switching to the backup channel.
All attempts to find a trivial auto-switch led to a bunch of 1-2 scripts written "for themselves" and not very configurable. Not one of them was attached to dhcp, which means that any manipulations on the provider's side required intervention in the settings. I myself wrote these at one time, but now I decided to design it beautifully on the new system - just as the Debian developers intended, namely, changing the configuration files, adding our scripts and not touching the ones that the system provided to us.
So we have:
- two cables from two providers, both give IP by dhcp
- a freshly assembled server running debian wheezy with three network cards (maybe I'll add more later)
- the desire that the Internet does not disappear (work does not wait!). Balancing, etc. leave for later.
The logic at first glance is simple:
- we ping some host in turn through different interfaces
- if the ping is unstable, we switch to the backup channel.
But only in the implementation the goal was set to do everything as flexible as possible, for example, not to limit the number of potential providers and subsequently make a minimum of body movements for reconfiguration.
First, let's see what kind of buns are already in the system.
In the / etc / network folder, we are interested in the interfaces file and the if-down.d, if-post-down.d, if-pre-up.d, if-up.d folders.
When manipulating interfaces through ifup / ifdown, run-parts are set on folders, and the following environment variables are accessible to scripts in them: IFACE, LOGICAL, ADDRFAM, METHOD, MODE, PHASE, MODE, VERBOSITY, PATH
When the system starts, scripts from the if folder are first run -pre-up.d (once for each interface, but before them comes IFACE = ”- all”, then the interfaces rise and scripts from the if-up.d and IFACE = ”- all” folders are run already in At the end, ifup ethX runs only once for ethX (without “–all"). Similarly, if-down.d and if-post-down.d when ifdown and system shutdown.
The system allows you to assign a script for each operation and for each interface, but to make similar changes every 10 out of 20 scripts was not included in my plans, so we will write one large script and arrange symlinks from all four folders on it. You can understand where it is launched from by environment variables.
However, we still need to know the gateway information that came from dhcp. For this case, there are also folders with scripts /etc/dhcp/dhclient-enter-hooks.d and /etc/dhcp/dhclient-exit-hooks.d
The startup sequence is as follows:
- polled the dhcp server
- launched the contents of the dhclient-enter-hooks folder .d
- configured network parameters (ip, DNS, gateway, ...)
- launched the contents of the dhclient-exit-hooks.d folder
The scripts also have access to various useful variables (parameters that came from the dhcp server), some of which we will need to save.
After several evenings, the following happened:
All scripts are in the / etc / network / scripts folder. From different folders there are symlinks
Settings - / etc / default / network-scripts
Temporary files put / var / lib / dhcp
Logs are written to the file /var/log/network-scripts.log
Settings
Everything should be clear here. Interfaces LAN, WAN can be added as much as you like. In the WAN list, the first is the highest priority, then in descending order.
Separate file with functions.
Here we have:
- import settings from / etc / default / network-scripts
- logging (log, warn),
- launching commands with writing to the log parameters and results of work
- update_local_redirect () adds routes to port 80 by transparent proxy
- update_squid () adds a rule for transparent proxy itself (runs in /etc/init.d/squid3 - this is the only system script I had to get into)
Here and thereafter, we use the technology that I invented several years ago with the variables $ ADD and $ INS for iptables . Allows you to write a rule in only one place, and then add-delete it, changing only these variables.
- Save new_routers and new_ip_address to a file (then they will be understood)
- default route is allowed only for the priority interface
- Add a static route for sites with billing providers. I do not need a provider’s lock, but you can also add it. Authentication by DNS servers.
- added a reconfiguration for the RENEW mode (if suddenly something changes at the provider), but has not yet tested it.
Firewall rules. For general tables, we write at the time of pre-up and post-down, for NAT - in post-up and pre-down.
It's simple: we ping in order of priority. Found the best - switch. If anything, write to the log.
And finally
Simlink
All attempts to find a trivial auto-switch led to a bunch of 1-2 scripts written "for themselves" and not very configurable. Not one of them was attached to dhcp, which means that any manipulations on the provider's side required intervention in the settings. I myself wrote these at one time, but now I decided to design it beautifully on the new system - just as the Debian developers intended, namely, changing the configuration files, adding our scripts and not touching the ones that the system provided to us.
So we have:
- two cables from two providers, both give IP by dhcp
- a freshly assembled server running debian wheezy with three network cards (maybe I'll add more later)
- the desire that the Internet does not disappear (work does not wait!). Balancing, etc. leave for later.
The logic at first glance is simple:
- we ping some host in turn through different interfaces
- if the ping is unstable, we switch to the backup channel.
But only in the implementation the goal was set to do everything as flexible as possible, for example, not to limit the number of potential providers and subsequently make a minimum of body movements for reconfiguration.
First, let's see what kind of buns are already in the system.
In the / etc / network folder, we are interested in the interfaces file and the if-down.d, if-post-down.d, if-pre-up.d, if-up.d folders.
root@ns:/etc/network# cat interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
iface lo inet loopback
# The primary network interface
iface eth0 inet static
address 192.168.104.1
netmask 255.255.255.0
iface eth1 inet dhcp
iface eth3 inet dhcp
When manipulating interfaces through ifup / ifdown, run-parts are set on folders, and the following environment variables are accessible to scripts in them: IFACE, LOGICAL, ADDRFAM, METHOD, MODE, PHASE, MODE, VERBOSITY, PATH
When the system starts, scripts from the if folder are first run -pre-up.d (once for each interface, but before them comes IFACE = ”- all”, then the interfaces rise and scripts from the if-up.d and IFACE = ”- all” folders are run already in At the end, ifup ethX runs only once for ethX (without “–all"). Similarly, if-down.d and if-post-down.d when ifdown and system shutdown.
The system allows you to assign a script for each operation and for each interface, but to make similar changes every 10 out of 20 scripts was not included in my plans, so we will write one large script and arrange symlinks from all four folders on it. You can understand where it is launched from by environment variables.
However, we still need to know the gateway information that came from dhcp. For this case, there are also folders with scripts /etc/dhcp/dhclient-enter-hooks.d and /etc/dhcp/dhclient-exit-hooks.d
The startup sequence is as follows:
- polled the dhcp server
- launched the contents of the dhclient-enter-hooks folder .d
- configured network parameters (ip, DNS, gateway, ...)
- launched the contents of the dhclient-exit-hooks.d folder
The scripts also have access to various useful variables (parameters that came from the dhcp server), some of which we will need to save.
After several evenings, the following happened:
All scripts are in the / etc / network / scripts folder. From different folders there are symlinks
Settings - / etc / default / network-scripts
Temporary files put / var / lib / dhcp
Logs are written to the file /var/log/network-scripts.log
Settings
# cat /etc/default/network-scripts
# Configuration file for /etc/network/scripts/*
# Host to ping for autoroute
HOST_TO_PING="4.2.2.1"
# Number of pings to check the connection
PING_COUNT=5
# list of LAN interfaces
IFLAN="eth0"
# WAN prio from first to last
IFWAN="eth1 eth3"
# open ports from WAN zone
WAN_PORTS_OPEN=""
Everything should be clear here. Interfaces LAN, WAN can be added as much as you like. In the WAN list, the first is the highest priority, then in descending order.
Separate file with functions.
# cat /etc/network/scripts/functions
#!/bin/sh
DHCPLIB="/var/lib/dhcp"
LOGDIR="/var/log"
LOGFILE="$LOGDIR/network-scripts.log"
HOST_TO_PING="4.2.2.1"
PING_COUNT=3
SQUID_PORT="3128"
IFLAN=""
IFWAN=""
WAN_PORTS_OPEN=""
. /etc/default/network-scripts
# Local variables
DEFAULTWAN=${IFWAN% *}
log()
{
DATE=`date`
echo "$DATE $@" >> $LOGFILE
}
warn()
{
log "WARNING: $@"
echo "WARNING: $@"
}
cmd()
{
$@
RES=$?
log "$RES - $@"
return $RES
}
get_ip()
{
IP=`ip addr list $1 | grep " inet " | head -n 1 | cut -d " " -f 6 | cut -d / -f 1`
}
update_local_redirect()
{
for i in $IFLAN; do
cmd iptables -t nat $INS PREROUTING -i $i -p tcp --dport 80 -d $1 -j ACCEPT
done
}
update_squid()
{
case $1 in
start)
ADD="-A"
INS="-I"
;;
stop)
ADD="-D"
INS="-D"
;;
*)
ADD="-C"
INS="-C"
esac
for i in $IFLAN; do
# transparent proxy
cmd iptables -t nat $ADD PREROUTING -i $i -p tcp --dport 80 -j REDIRECT --to-port $SQUID_PORT
done
}
Here we have:
- import settings from / etc / default / network-scripts
- logging (log, warn),
- launching commands with writing to the log parameters and results of work
- update_local_redirect () adds routes to port 80 by transparent proxy
- update_squid () adds a rule for transparent proxy itself (runs in /etc/init.d/squid3 - this is the only system script I had to get into)
Here and thereafter, we use the technology that I invented several years ago with the variables $ ADD and $ INS for iptables . Allows you to write a rule in only one place, and then add-delete it, changing only these variables.
# cat /etc/network/scripts/route-enter
#!/bin/sh
. /etc/network/scripts/functions
log "$0 route-enter ${interface} ${reason} ${new_routers}"
# security bugfix
new_host_name=${new_host_name//[^-.a-zA-Z0-9]/}
# save routers to special file
echo -n ${new_routers} > $DHCPLIB/routers.${interface}
echo -n ${new_ip_address} > $DHCPLIB/ip_address.${interface}
case ${interface} in
$DEFAULTWAN)
# by default enable routers only for first WAN interface
;;
*)
# and clear it for others
unset new_routers
;;
esac
- Save new_routers and new_ip_address to a file (then they will be understood)
- default route is allowed only for the priority interface
# cat /etc/network/scripts/route-exit
#!/bin/sh
. /etc/network/scripts/functions
log "$0 route-exit ${interface} ${reason}"
update_routes()
{
cmd route $ADD -host $HOST_TO_PING gw ${routers}
# identyfy providers by DNS addresses
case $DNS in
*82.193.96*)
DESTIP=`resolveip -s stat.ipnet.ua`
cmd route $ADD -host $DESTIP gw ${routers}
;;
*193.41.63*|*192.168.11.1*)
DESTIP=`resolveip -s my.kyivstar.ua`
cmd route $ADD -host $DESTIP gw ${routers}
;;
*)
warn "route-exit - unknown DNS ${new_domain_name_servers} specified"
;;
esac
}
case ${reason} in
BOUND)
ADD="add"
DNS=${new_domain_name_servers}
# use saved-to-file value due to $old_routers can be cleared for some interfaces by other script
routers=`cat $DHCPLIB/routers.${interface}`
update_routes
;;
RELEASE)
# No need to delete routes during release
# ADD="del"
# routers=${old_routers}`
# update_routes
;;
PREINIT)
;;
RENEW)
if [ "$old_routers" != "$new_routers" ]; then
ADD="del"
DNS=${old_domain_name_servers}
routers=${old_routers}
update_routes
ADD="add"
DNS=${new_domain_name_servers}
routers=`cat $DHCPLIB/routers.${interface}`
update_routes
fi
if [ "$old_ip_address" != "$new_ip_address" ]; then
ADD="-D"
INS="-D"
update_local_redirect ${old_ip_address}
ADD="-A"
INS="-I"
update_local_redirect ${new_ip_address}
fi
;;
*)
warn "route-exit - unknown reason ${reason} used"
;;
esac
- Add a static route for sites with billing providers. I do not need a provider’s lock, but you can also add it. Authentication by DNS servers.
- added a reconfiguration for the RENEW mode (if suddenly something changes at the provider), but has not yet tested it.
# cat /etc/network/scripts/firewall
#!/bin/bash
. /etc/network/scripts/functions
get_ip $IFACE
log "$0 $IFACE $LOGICAL $ADDRFAM $METHOD $MODE $PHASE $VERBOSITY $IP"
case $MODE in
start)
INS="-I"
ADD="-A"
echo -n $IP > $DHCPLIB/ip_address.$IFACE
;;
stop)
INS="-D"
ADD="-D"
echo -n > $DHCPLIB/ip_address.$IFACE
;;
*)
INS="-C"
ADD="-C"
warn "Wrong MODE:$MODE"
;;
esac
case $IFACE in
--all)
case $PHASE in
pre-down|post-up)
# skip proxy for local addresses
for j in $IFLAN $IFWAN; do
get_ip $j
update_local_redirect $IP
done
;;
post-up|pre-down)
;;
esac
;;
lo)
;;
*)
if [[ "$IFLAN" == *$IFACE* ]]; then
# LAN
case $PHASE in
pre-up|post-down)
cmd iptables $INS INPUT -p tcp -i $IFACE --dport 22 -j ACCEPT
;;
post-up|pre-down)
;;
*)
warn "Wrong PHASE:$PHASE"
;;
esac
fi
if [[ "$IFWAN" == *$IFACE* ]]; then
# WAN
case $PHASE in
pre-up|post-down)
# by default close all input connections
cmd iptables $ADD INPUT -p tcp -i $IFACE --dport 1:10000 -j DROP
cmd iptables $ADD INPUT -p udp -i $IFACE --dport 1:10000 -j DROP
# open ports from list
for PORT in $WAN_PORTS_OPEN; do
cmd iptables $INS INPUT -p tcp -i $IFACE --dport $PORT -j ACCEPT
done
;;
post-up|pre-down)
cmd iptables -t nat $ADD POSTROUTING -o $IFACE -j MASQUERADE
;;
*)
warn "Wrong PHASE:$PHASE"
;;
esac
fi
;;
esac
Firewall rules. For general tables, we write at the time of pre-up and post-down, for NAT - in post-up and pre-down.
# cat /etc/network/scripts/autoroute
#!/bin/sh
# Script for cron to monitor WAN interfaces
# and (in future) SQUID status
. /etc/network/scripts/functions
CURRENT_ROUTE_DEV=`ip route show | grep default | awk '{print $5}'`
unset ROUTE_GOOD
PING_RESULTS=""
for i in $IFWAN; do
if [ -z $ROUTE_GOOD ]; then
PING_RESULT=`ping -c$PING_COUNT -q $HOST_TO_PING -I $i | grep 'packet loss' | awk '{print $6}'`
# If no route t host then set to 100% loss
if [ -z $PING_RESULT ]; then
warn "$0 No route to host $HOST_TO_PING on $i"
PING_RESULT='100%'
fi
if [ $PING_RESULT = '0%' ]; then
ROUTE_GOOD=$i
if [ -z $CURRENT_ROUTE_DEV ]; then
log "$0 Adding default route to $i"
cmd route add default gw `cat $DHCPLIB/routers.$i`
elif [ $CURRENT_ROUTE_DEV != $i ]; then
log "$0 Change default route from $CURRENT_ROUTE_DEV to $i"
cmd route del default
cmd route add default gw `cat $DHCPLIB/routers.$i`
fi
else
log "$0 loss $PING_RESULT on $i"
fi
fi
PING_RESULTS="$PING_RESULTS $PING_RESULT"
done
if [ -z $ROUTE_GOOD ]; then
warn "$0 lost all internet connections ($PING_RESULTS loss)"
fi
It's simple: we ping in order of priority. Found the best - switch. If anything, write to the log.
And finally
# cat /etc/cron.d/autoroute
PATH="/usr/bin:/bin:/usr/sbin:/sbin"
*/5 * * * * root /etc/network/scripts/autoroute
# cat /etc/logrotate.conf | tail
# system-specific logs may be configured here
/var/log/network-scripts.log {
weekly
missingok
rotate 7
compress
}
Simlink
/etc/dhcp/dhclient-enter-hooks.d/route-enter -> ../../network/scripts/route-enter
/etc/dhcp/dhclient-exit-hooks.d/route-exit -> ../../network/scripts/route-exit
/etc/network/if-pre-up.d/firewall -> ../scripts/firewall
/etc/network/if-down.d/firewall -> ../scripts/firewall
/etc/network/if-up.d/firewall -> ../scripts/firewall
/etc/network/if-post-down.d/firewall -> ../scripts/firewall