#!/usr/bin/bash
#
# Interface Setup
# Copyright (C) 2019-2024 by Thomas Dreibholz
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contact: dreibh@simula.no

# Bash options:
set -e


# ###### Exit with usage information ########################################
exit_with_usage ()
{
   /bin/echo >&2 "Usage: $0 interface pre-up|up|post-up|pre-down|down|post-down ipv4|ipv6 [address/prefix gateway metric options] ..."
   exit 1
}


# ###### Write log information ##############################################
log ()
{
   /bin/echo -e "\x1b[1;34m`date +%FT%H:%M:%S`: $@\x1b[0m"
}


# ###### Wait for link on given interface ###################################
# $1 = Interface
wait_for_link ()
{
   echo -en "Waiting for link "
   i=0
   while [ $i -lt 25 ] ; do
      if [ -e /sbin/ethtool ] ; then
         link=`/sbin/ethtool $1 | grep "Link detected" | sed -e "s/[ \t]*Link detected: //g"`
      else
         link="unknown"
      fi
      echo -en "."
      if [ "$link" = "yes" ] ; then
         break
      fi
      sleep 0.25
      let i=i+1
   done
   echo " done."
}


# ###########################################################################
# #### Main Program                                                      ####
# ###########################################################################

# ====== Handle arguments ===================================================
if [ $# -lt 3 ] ; then
   exit_with_usage
fi

logFile="/var/log/nornet-ifupdown.log"
interface="$1"
state="$2"
type="$3"
if [ "$type" != "ipv4" -a "$type" != "ipv6" ] ; then
   exit_with_usage
fi

if [ "$state" = "pre-up" ] ; then
   (
      log "Pre-Up of interface $interface/$type"
      if [ "$type" == "ipv6" ] ; then
         interfaceTag="${interface/./\/}"
         /sbin/sysctl -q -w net.ipv6.conf.$interfaceTag.use_tempaddr=0 net.ipv6.conf.$interfaceTag.accept_ra=0 net.ipv6.conf.$interfaceTag.autoconf=0 || true
      fi

      # NOTE:
      # For some reason, listening to the solicited node multicast
      # address sometimes does not work. Then, IPv6 connectivity is lost.
      # Turning on promiscuous mode as a work-around here!
      /sbin/ip link set up promisc on dev "$interface" || true

      # NOTE:
      # The interface is going up now. It may take a few seconds until
      # auto-negotiation is done. When the interface is finally ready,
      # the kernel seems to remove IPv6 addresses. As work-around, try
      # to wait a moment until the interface is actually up before
      # configuring addresses.
      wait_for_link $interface
   ) >>$logFile 2>&1

elif [ "$state" = "post-up" ] ; then
   (
      log "Post-Up of interface $interface/$type"
      if [ "$type" == "ipv4" ] ; then
         ip -4 addr show dev "$interface"
      else
         ip -6 addr show dev "$interface"
      fi
   ) >>$logFile 2>&1

elif [ "$state" = "post-down" ] ; then
   (
      log "Post-Down of interface $interface/$type"
      /sbin/ip link set down promisc off dev "$interface" || true
   ) >>$logFile 2>&1

elif [ "$state" = "up" ] ; then
   if [ $# -lt 2 ] ; then
      exit_with_usage
   fi
   shift ; shift ; shift

   (
      log "Up of interface $interface/$type"

      while [ "$4" != "" ] ; do
         address="$1"
         network="$2"
         gateway="$3"
         metric="$4"
         options="$5"
         source=`echo "$1" | sed -e "s/\/[0-9]*$//g"`
         shift ; shift ; shift ; shift ; shift
         /bin/echo "Adding $address, route to $network src $source via $gateway with metric $metric ..."
         /sbin/ip addr add "$address" dev "$interface" valid_lft forever nodad $options || /bin/echo "ERROR: Failed to add address" || true
         /sbin/ip route add "$network" via "$gateway" dev "$interface" src "$source" metric "$metric" || /bin/echo "ERROR: Failed to add route" || true
      done

   ) >>$logFile 2>&1

elif [ "$state" = "down" ] ; then
   if [ $# -lt 2 ] ; then
      exit_with_usage
   fi
   shift ; shift ; shift

   (
      log "Down of interface $interface/$type"

      while [ "$4" != "" ] ; do
         address="$1"
         network="$2"
         gateway="$3"
         metric="$4"
         options="$5"
         shift ; shift ; shift ; shift ; shift
         /bin/echo "Removing $address, route to $network via $gateway with metric $metric ..."
         /sbin/ip route del "$network" via "$gateway" dev "$interface" metric "$metric" || /bin/echo "ERROR: Failed to remove route"
         /sbin/ip addr del "$address" dev "$interface" $options || /bin/echo "ERROR: Failed to remove address"
      done

   ) >>$logFile 2>&1

else
   exit_with_usage

fi
