📜 ⬆️ ⬇️

Switching between ISPs on Debian 7

In the latest Linux distributions there are quite a lot of all sorts of useful buns in the / etc folder, but few use them wisely. 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 banal autoswitch resulted in a bundle of 1-2 scripts written “for themselves” and little configurable. None of them was tied to dhcp, which means any manipulations on the side of the provider required intervention in the settings. I wrote these myself in due time, but now I decided to design it beautifully on the new system, in the way the Debian developers conceived, namely, we change the configuration files, add our own scripts and don’t touch the ones provided by the system.

So, we have:
- two cables from two providers, both give IP via dhcp
- freshly assembled server running debian wheezy with three network card (maybe later I will add more)
- the desire that the Internet does not disappear (the 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, switch to the backup channel.
Only in the implementation was the goal to do everything as flexibly as possible, for example, not to limit the number of potential providers and subsequently to do a minimum of gestures 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-up.d, if-pre-up.d, and 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 via ifup / ifdown, run-parts are set on daddies and the following environment variables are available to scripts: IFACE, LOGICAL, ADDRFAM, METHOD, MODE, PHASE, MODE, VERBOSITY, PATH
When the system is started, the scripts from the if-pre-up.d folder are launched first (once for each interface, but IFACE = ”- all” comes before them, then the interfaces are raised and the scripts from the if-up.d and IFACE folder are launched = "- all" comes at the end. With ifup, ethX is run only once for ethX (without the “–all”). Similar to if-down.d and if-post-down.d during ifdown and system shutdown.
The system allows you to assign a script for each operation and interface for each interface, but to make similar changes each time in 10 out of 20 scripts was not included in my plans, so we will write one big script and arrange symlinks from all four folders on it. You can understand where it is started from by environment variables.

However, we still need to know the information about the gateways that came through dhcp. In 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:
- polled dhcp server
- launched the contents of the dhclient-enter-hooks.d folder
- configured network parameters (ip, DNS, gateway, ...)
- launched the contents of the dhclient-exit-hooks.d folder
Different useful variables (parameters that came from the dhcp server) are also available to the scripts, some of which we will need to save.
')
After several evenings, the following happened:
All scripts are in the / etc / network / scripts folder. Symlinks lead from different folders
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, you can add as many as you want. In the WAN list, the first is the highest priority, then descending.
Separately 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),
- run commands with recording in the log parameters and work results
- update_local_redirect () adds routes to port 80 past the transparent proxy
- update_squid () adds a rule for the transparent proxy itself (runs in /etc/init.d/squid3 - this is the only system script that you had to get into)
Hereinafter, the technology that I invented a few years ago with the variables $ ADD and $ INS for iptables is used. 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 you'll need it)
- 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 static route for sites with billing providers. I don’t need a local provider, but you can add it too. Identification by DNS servers.
- for the RENEW mode, added a reconfiguration (if something suddenly changes with 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 moment 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 

Everything is simple: ping in order of priority. Found the best - switch. If anything, we 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 } 


Simlinki
 /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 

Source: https://habr.com/ru/post/199478/


All Articles