#!/bin/bash
# this adds the route we need if it is missing, otherwise quits
VERSION="0.9.1 [12 Mar 2026]"
THIS=$(basename "$0")
COLUMNS=$(stty size 2>/dev/null||echo 80); COLUMNS=${COLUMNS##* }

valid_ipv4() {
	# Return code 0 if parameter1 is valid dotted decimal ipv4, 2 if it is valid CIDR (/16->/32), else 1. No text output.
	# If optional parameter2 is set (to anything) and parameter1 is not a CIDR then it treats a valid 'reserved' ip
	#   (e.g. local LAN ip) as invalid - see https://en.wikipedia.org/wiki/Reserved_IP_addresses
	# Example      : valid_ipv4 $ip no_local || echo "'$ip' is not a valid global ip"
	# Notes        : return code 1 for unusual formats e.g. leading zeroes (032.013.123.345) or with spaces (19. 23.  3.100)
	#                derived from code at https://stackoverflow.com/questions/50084229/bash-check-if-cidr-address-is-valid
	# Version      : v2.60312
	local IFS
	local IP
	local QUATRAIN
	local OK_RETURN_CODE
	OK_RETURN_CODE=0
	echo "$1"|grep -q "[^./[:digit:]]" && { [[ -n $DEBUG ]] && echo "Not all digits or slashes or full stops"; return 1; } # fail if not all digits slashes or full stops
	echo "$1"|grep -q "\b0[0-9]" && { [[ -n $DEBUG ]] && echo "Leading zeroes"; return 1; } # fail if any quatrains start with leading zeroes (but an actual zero quatrain is permitted)
	[[ $(echo "$1"|awk -F'.' '{print NF}') -eq 4 ]] || { [[ -n $DEBUG ]] && echo "Not 4 dot-separated quatrains"; return 1; } # fail if not 4 dot-separated quatrains
	# Parse "a.b.c.d/n" into five separate variables
	IFS="./" read -ra QUATRAIN <<< "$1"
	[[ ${#QUATRAIN[@]} -eq 4 || ${#QUATRAIN[@]} -eq 5 ]] || return 1 # fail if there are not 4 (for IP) or 5 (for CIDR) extracted values
	# Convert IP address from quad notation to integer
	IP=$((QUATRAIN[0] * 256 ** 3 + QUATRAIN[1] * 256 ** 2 + QUATRAIN[2] * 256 + QUATRAIN[3]))
	if [[ -z ${QUATRAIN[4]} ]]; then
		QUATRAIN[4]=32 # not a CIDR so simulate with '/32'
	elif [[ ${QUATRAIN[4]} -ge 0 && ${QUATRAIN[4]} -le 32 ]]; then
		OK_RETURN_CODE=2 # valid CIDR suffix
	else
		return 1 # bad CIDR suffix
	fi
	# Remove upper bits and check that all $N lower bits are 0
	if [[ $((IP % 2**(32-QUATRAIN[4]))) == 0 ]]; then
		if [[ $OK_RETURN_CODE -eq 0 && -n $2 ]]; then
			# exclude reserved IPs (not if CIDR)
			OK_RETURN_CODE=$(( 1 - $(echo "$1"|grepcidr -cxve '0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 192.88.99.0/24 192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 224.0.0.0/4 233.252.0.0/24 240.0.0.0/4 255.255.255.255/32') ))
		fi
		return $OK_RETURN_CODE
	else
		return 1 # NOT OK!
	fi
}

DEV=$(ip -4 address show up scope global|head -n1|awk -F"[ :]*" '{print $2}')

while getopts ":hlqw" optname; do
	case "$optname" in
		"h")	HELP="y";;
		"l")	CHANGELOG="y";;
		"q")	QUIET="y";;
		"w")	COLUMNS=30000;; #suppress line-breaking
		"?")	echo "Unknown option $OPTARG"; exit 1;;
		*)		echo "Unknown error while processing options"; exit 1;;
	esac
done
shift $((OPTIND-1))
[[ -z $QUIET || -n $HELP$CHANGELOG ]] && echo -e "\n$THIS v$VERSION by Dominic (try -h for help)\n${THIS//?/=}\n"
if [[ -n $HELP ]]; then
	echo -e "If there is no default route for ip traffic, $THIS adds one based \
on the most recent information from previous leases. \
One situation where this can be needed is if you turn off wifi (e.g. at end of \
booting, using https://www.timedicer.co.uk/programs/help/wifi-updown.sh.php, \
because you have wired connection). For use under GNU/Linux.

Options: -h  show this help and exit
         -l  show changelog and exit
         -q  be quiet

Exit Codes: Note that a non-zero exit code may not indicate an error -
            0 no change was required
            1 an error occurred
            10 default route was added successfully

Dependencies: logger[util-linux]

License: Copyright © 2026 Dominic Raferd. Licensed under the Apache License, \
Version 2.0 (the \"License\"); you may not use this file except in compliance \
with the License. You may obtain a copy of the License at \
https://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable \
law or agreed to in writing, software distributed under the License is \
distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY \
KIND, either express or implied. See the License for the specific language \
governing permissions and limitations under the License.
"|fold -sw"$COLUMNS"
fi
if [[ -n $CHANGELOG ]]; then
	[[ -n $HELP ]] && echo "Changelog:"
	echo "\
0.9 [27 Nov 2023] - minor output bugfix
0.8 [26 Oct 2023] - better shellcheck conformity, modified exit code on success to 10, no logging when running from terminal
0.7 [09 Oct 2023] - add display of start date/time
0.6 [29 Dec 2019] - remove dependency on if(up|down|query)
0.5 [14 Aug 2019] - added exit code information to help
0.4 [20 Jul 2019] - log message even if nothing needs changing
0.3 [09 Nov 2018] - changed help, log a message in syslog, add dependency test
0.2 [14 Sep 2018] - fixes
0.1 [13 Sep 2018] - initial version"
fi
[[ -n $HELP$CHANGELOG ]] && exit 0
for PROG in ip logger; do
	command -v $PROG >/dev/null 2>&1 || { echo "command '$PROG' not available, $THIS cannot work" >&2; exit 1; }
done
EXITCODE=0
CIDR_IP=$(ip -o address|grep -v " lo "|awk '{print $4}') # this is just for info
IFS=" " read -r -a DEFAULT_DEV<<< "$(ip route show default)"
# define gateway as most recent entry in lease files for a 'router', or if none such then for 'dhcp-server-identifier'
for GW in router dhcp-server-identifier; do
	# shellcheck disable=SC2046
	SERVER=$(grep -h "option $GW" -- $(ls /var/lib/dhcp -lrt|awk '{if (NF>2) print "/var/lib/dhcp/" $NF}')|tail -n1|awk -F"[ ;]" '{print $(NF-1)}')
	[[ -n $SERVER ]] && break
done
[[ -z $QUIET ]] && echo -e "Started: $(date +'%F %T')\nOur ip: $CIDR_IP\nOur most recent $GW was: $SERVER"
if [[ -n ${DEFAULT_DEV[4]} ]]; then
	[[ ${DEFAULT_DEV[4]} != "$DEV" ]] && echo "Advisory: default route does not use identified device '$DEV'"
	[[ ${DEFAULT_DEV[2]} != "$SERVER" ]] && echo "Advisory: default route does not use identified $GW '$SERVER'"
	[[ ! -t 0 ]] && logger -t "$THIS" "Routing table has a default route, nothing to change"
	[[ -z $QUIET ]] && { echo -e "Routing table has a default route, nothing to change:"; ip route show|tr -d '\n'; }
else
	if [[ $(id -u) -ne 0 ]]; then
		echo "Routing needs update but you must be root to do this, sorry" >&2
		EXITCODE=1
	else
		valid_ipv4 "$SERVER" || { echo "Unable to locate valid ipv4 gateway, found $GW $SERVER, aborting" >&2; exit 1; }
		ip route add default via "$SERVER" || { echo "Unable to fix routing by adding $GW $SERVER" >&2; exit 1; }
		[[ ! -t 0 ]] && logger -t "$THIS" "No default route found - added one via $GW $SERVER"
		EXITCODE=10
		echo "Default routing (via $GW $SERVER) fixed by $THIS:"
	fi
	unset QUIET
fi
[[ -z $QUIET ]] && ip route show|sed 's/^/  /'
exit $EXITCODE
