Table of Contents

Due connessioni a internet per un solo host

Questa configurazione permette a un host di avere due connessioni internet sempre attive. L'host e` accessibile da tutte e due le connessioni indistintamente, e le usa tutte e due in modo bilanciato per il traffico uscente. Usando uno script apposito (ho scopiazzato orrendamente, lo ammetto) e` possibile avere un controllo sulla connettivita` delle due connessioni e provvedere a smettere di usare quella che per qualche motivo dovesse andare offline. Questo ovviamente non impatta le connessioni entranti, ma solo quelle uscenti.

Attenzione: questa configurazione non e` stata provata come sistema per dare connettivita` a degli host nattati dietro a una terza interfaccia. Potrebbe funzionare (con due regole di nat) ma anche no.

Configurazione statica

Questa configurazione definisce le regole necessarie per il routing verso due gateway diversi. E` necessario avere due schede di rete, una per connessione.

200 Fastweb
201 Ehiweb
# prima connessione: subnet 93.33.25.248/29, gw 249, il mio host e` il 253
auto ens18
iface ens18 inet static
        address 93.33.25.253/29
        post-up ip route add 93.33.25.248/29 dev ens18 src 93.33.25.253 table Fastweb
        post-up ip route add default via 93.33.25.249 table Fastweb
        post-up ip rule add from 93.33.25.253 table Fastweb
        # post-up ip rule add from all fwmark 0x1 table Fastweb 
        post-down ip rule del from 93.33.25.253 table Fastweb
        # post-down ip rule del from all fwmark 0x1 table Fastweb 


# seconda connessione: subnet 109.23.6.48/29, gw 49, il mio host e` il 53
auto ens19
iface ens19 inet static
        address 109.23.6.53/29
        post-up ip route add 109.23.6.48/29 dev ens19 src 109.23.6.53 table Ehiweb
        post-up ip route add default via 109.23.6.49 table Ehiweb
        post-up ip rule add from 109.23.6.53 table Ehiweb
        #post-up ip rule add from all fwmark 0x2 table Ehiweb
        post-down ip rule del from 109.23.6.53 table Ehiweb
        #post-down ip rule del from all fwmark 0x2 table Ehiweb

A questo punto abbiamo due interfacce ma nessun default routing. Per stabilire un routing di default statico, possiamo aggiungerlo come post-up all'ultima delle due interfacce, in questo modo:

# sotto alla interfaccia ens19, come ultima riga:
post-up ip route add default scope global nexthop via 93.33.25.249 dev ens18 weight 1 nexthop via 109.23.6.49 dev ens19 weight 1

Se vogliamo che il traffico uscente eviti di usare una interfaccia che non funziona, occorre, INVECE del routing statico, usare uno script che effettui un controllo costante e cambi le regole di conseguenza.

Script per il controllo del gateway morto

Questo script puo` essere usato per il controllo delle due connettivita`. Provvede a cambiare il default gateway a seconda di quale connessione funziona e quale no. Questo script e` stato copiato, non e` mio. L'autore e` Steve Buzonas e questo e` il link alla versione originale: https://github.com/sbuzonas/multiwan

Installazione

 #!/usr/bin/env bash

source /etc/multiwan.conf

STARTUP=1
CHECK_INTERVAL=1

# IP address of each WAN interface
WAN_NET1="$(ip addr show $WAN_IF1 | grep "inet " | tr -s [:space:] | cut -d ' ' -f3)"
WAN_NET2="$(ip addr show $WAN_IF2 | grep "inet " | tr -s [:space:] | cut -d ' ' -f3)"

WAN_IP1="$(echo $WAN_NET1 | cut -d '/' -f1)"
WAN_IP2="$(echo $WAN_NET2 | cut -d '/' -f1)"

# Last link status.  Defaults to down to force check of both on first run.
LLS1=1
LLS2=1

# Last ping status.
LPS1=1
LPS2=1

# Current ping status.
CPS1=1
CPS2=1

# Change link status.
CLS1=1
CLS2=1

# Count of consecutive status checks
COUNT1=0
COUNT2=0

function link_status() {
  case $1 in
    0)
      echo "Up" ;;
    1)
      echo "Down" ;;
    *)
      echo "Unknown" ;;
  esac
}

# check_link $IP $TIMEOUT
function check_link() {
  ping -W $2 -I $1 -c 1 $PING_TARGET > /dev/null 2>&1
  RETVAL=$?
  if [ $RETVAL -ne 0 ] ; then
    STATE=1
  else
    STATE=0
  fi

  link_status $STATE

  return $STATE
}

while : ; do
  LINK_STATE="$(check_link $WAN_IP1 $PING_TIMEOUT)"
  CPS1=$?

  if [ $LPS1 -ne $CPS1 ] ; then
    logger -p local6.notice -t MULTIWAN[$$] "Ping state changed for $WAN_TABLE1 from $(link_status $LPS1) to $(link_status $CPS1)"
    COUNT1=1
  else
    if [ $LPS1 -ne $LLS1 ] ; then
      COUNT1=`expr $COUNT1 + 1`
    fi
  fi

  if [[ $COUNT1 -ge $SUCCESS_COUNT || ($LLS1 -eq 0 && $COUNT1 -ge $FAILURE_COUNT) ]]; then
    CLS1=0
    COUNT1=0

    if [ $LLS1 -eq 1 ] ; then
      LLS1=0
    else
      LLS1=1
    fi
    logger -p local6.notice -t MULTIWAN[$$] "Link state for $WAN_TABLE1 is $(link_status $LLS1)"
  else
    CLS1=1
  fi

  LPS1=$CPS1

  LINK_STATE="$(check_link $WAN_IP2 $PING_TIMEOUT)"
  CPS2=$?

  if [ $LPS2 -ne $CPS2 ] ; then
    logger -p local6.notice -t MULTIWAN[$$] "Ping state changed for $WAN_TABLE2 from $(link_status $LPS2) to $(link_status $CPS2)"
    COUNT2=1
  else
    if [ $LPS2 -ne $LLS2 ] ; then
      COUNT2=`expr $COUNT2 + 1`
    fi
  fi

  if [[ $COUNT2 -ge $SUCCESS_COUNT || ($LLS2 -eq 0 && $COUNT2 -ge $FAILURE_COUNT) ]]; then
    CLS2=0
    COUNT2=0

    if [ $LLS2 -eq 1 ]; then
      LLS2=0
    else
      LLS2=1
    fi
    logger -p local6.notice -t MULTIWAN[$$] "Link state for $WAN_TABLE2 is $(link_status $LLS2)"
  else
    CLS2=1
  fi

  LPS2=$CPS2

  if [[ $CLS1 -eq 0 || $CLS2 -eq 0 ]] ; then
    if [[ $STARTUP -eq 1 ]] ; then
      STARTUP=0
      CHECK_INTERVAL=15
    fi
    if [[ $LLS1 -eq 1 && $LLS2 -eq 0 ]] ; then
      logger -p local6.notice -t MULTIWAN[$$] "Applying $WAN_TABLE2 only route."
      ip route change default scope global via $WAN_GW2 dev $WAN_IF2
    elif [[ $LLS1 -eq 0 && $LLS2 -eq 1 ]] ; then
      logger -p local6.notice -t MULTIWAN[$$] "Applying $WAN_TABLE1 only route."
      ip route change default scope global via $WAN_GW1 dev $WAN_IF1
    elif [[ $LLS1 -eq 0 && $LLS2 -eq 0 ]] ; then
      logger -p local6.notice -t MULTIWAN[$$] "Applying multiwan load balancing route."
      ip route replace default scope global nexthop via $WAN_GW1 dev $WAN_IF1 weight $WAN_WEIGHT1 nexthop via $WAN_GW2 dev $WAN_IF2 weight $WAN_WEIGHT2
    fi
  fi

  sleep $CHECK_INTERVAL
done
#!/bin/sh
### BEGIN INIT INFO
# Provides:          multiwan
# Required-Start:    $network $local_fs $remote_fs
# Required-Stop:     $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Monitor internet connection links and modify route depending upon link state
# Description:       This is a startup script for multiwan. Multiwan monitors multiple internet connections
#                    to provide failover routes depending upon link state.
### END INIT INFO

# Author: Steve Buzonas <steve@fancyguy.com>

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC=multiwan             # Introduce a short description here
NAME=multiwan             # Introduce the short server's name here
DAEMON=/usr/local/sbin/multiwan # Introduce the server's location here
DAEMON_ARGS=""             # Arguments to run the daemon with
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x $DAEMON ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
	# Return
	#   0 if daemon has been started
	#   1 if daemon was already running
	#   2 if daemon could not be started
	start-stop-daemon --start --quiet -b -m --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
		|| return 1
	start-stop-daemon --start --quiet -b -m --pidfile $PIDFILE --exec $DAEMON -- \
		$DAEMON_ARGS \
		|| return 2
}

#
# Function that stops the daemon/service
#
do_stop()
{
	# Return
	#   0 if daemon has been stopped
	#   1 if daemon was already stopped
	#   2 if daemon could not be stopped
	#   other if a failure occurred
	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
	RETVAL="$?"
	[ "$RETVAL" = 2 ] && return 2
	# Wait for children to finish too if this is a daemon that forks
	# and if the daemon is only ever run from this initscript.
	# If the above conditions are not satisfied then add some other code
	# that waits for the process to drop all resources that could be
	# needed by services started subsequently.  A last resort is to
	# sleep for some time.
	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
	[ "$?" = 2 ] && return 2
	# Many daemons don't delete their pidfiles when they exit.
	rm -f $PIDFILE
	return "$RETVAL"
}

case "$1" in
  start)
    [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
    do_start
    case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
  ;;
  stop)
	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
	do_stop
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  status)
       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
       ;;
  restart|force-reload)
	log_daemon_msg "Restarting $DESC" "$NAME"
	do_stop
	case "$?" in
	  0|1)
		do_start
		case "$?" in
			0) log_end_msg 0 ;;
			1) log_end_msg 1 ;; # Old process is still running
			*) log_end_msg 1 ;; # Failed to start
		esac
		;;
	  *)
	  	# Failed to stop
		log_end_msg 1
		;;
	esac
	;;
  *)
	echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
	exit 3
	;;
esac

:
PING_TARGET="8.8.8.8"
PING_TIMEOUT=2

SUCCESS_COUNT=4
FAILURE_COUNT=1

WAN_IF1="ens18"
WAN_IF2="ens19"

WAN_GW1="93.33.25.249"
WAN_GW2="109.23.6.49"

WAN_TABLE1="Fastweb"
WAN_TABLE2="Ehiweb"

WAN_WEIGHT1=1
WAN_WEIGHT2=1
insserv multiwan

A questo punto, riavviando la macchina (ce l'avete una console che funzioni anche SENZA LA RETE, vero? Se abbiamo sbagliato qualcosa, addio connessione di rete, ci siamo chiusi fuori per sempre!) dovremmo avere due interfacce di rete attive, e nel syslog vedremo anche le righe relative allo script multiwan che ci dice quali interfacce sta usando e come.

Test

La macchina dovrebbe essere raggiungibile da fuori a tutti e due i suoi indirizzi pubblici. Staccando una delle due linee l'altra dovrebbe rimanere totalmente funzionante, e la macchina dovrebbe (dopo qualche secondo) usare sempre la connessione funzionante, saltando dall'una all'altra automaticamente.

E il NAT?

Se il nostro host non deve fare nat verso altri host, siamo a posto così. Ma se volessimo fare NAT verso altri host, allora abbiamo due soluzioni: quella "comoda" è farci un firewall con Openwrt/LEDE e usare il pacchetto MWAN3 che fa tutto lui. Ma se vogliamo farlo a mano, occorre aggiungere un pochino di roba a questa config.

iptables -t nat -A POSTROUTING -o ens18  -j SNAT -s 192.168.2.0/24 --to-source 93.33.25.253
iptables -t nat -A POSTROUTING -o ens19  -j SNAT -s 192.168.2.0/24 --to-source 109.23.6.53
iptables -t nat -A PREROUTING -p tcp -i enps18 --dport 25 -j DNAT --to 192.168.2.2
iptables -t nat -A PREROUTING -p tcp -i enps19 --dport 25 -j DNAT --to 192.168.2.2
# marco con mark 1 le connessoni "new" che entrano dalla enps18
iptables -t mangle -A PREROUTING -p tcp -i enps18 --dport 25 -m conntrack --ctstate NEW -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -p tcp -i enps18 --dport 25 -m conntrack --ctstate NEW -j CONNMARK --save-mark

# marco con mark 2 le connessoni "new" che entrano dalla enps19
iptables -t mangle -A PREROUTING -p tcp -i enps19 --dport 25 -m conntrack --ctstate NEW -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -p tcp -i enps19 --dport 25 -m conntrack --ctstate NEW -j CONNMARK --save-mark

# dico a iptables di marcare (con mark 1 o 2, a seconda di come erano in origine) i pacchetti che tornano indietro.
iptables -t mangle -A PREROUTING -i <interfaccia LAN> -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark
post-up ip rule add from all fwmark 0x1 table Fastweb 
post-up ip rule add from all fwmark 0x2 table Ehiweb