#!/bin/bash

declare -A ip

ECHO=
IP_BASE_ADDR=10.1
IP6_BASE_ADDR=fd00:0:1

ROOT_NS=default
BRIDGE_NS=
NS_LIST=
ROUTES=
IF_NAME=eth
BR_NAME=br
CHAINS=
DEFAULT_CHAINS="TESTIN TESTOUT"
SCRIPT=
FIXED_LEN=0
NUM_LEN=

CMD=add
LOW=0
HIGH=10
SIDE=


show_help()
{
	cat <<EOF
$0 - Usage: $0 CMD [OPTIONS]

	CMD = add | del | remake

	Options:
	-h		Show this!
	-f NUM		First interface number
	-l NUM		Last interface number
	-p		fixed interface name lenths
	-n "NS NS .."	List of namespaces (default: high medium low default)
	-b NS		Specify namespace for bridge (default: medium)
	-r NS		Specify root namespace (default: default)
	-R		no root ns
	-N NS		Refresh namespace NS
	-c BRIDGE	Set connecting bridge name (default br)
	-i NAME		Set interface name (default eth)
	-4 IP_ADDR	First two octets of IPv4 address (default 10.1)
	-6 IP6_ADDR	Prefix of IPv6 address (default fd00:0:1)
	-q ROUTE@IF	Add route via IF num
	-Q IF		Add default route via IF num
	-I CHAIN	Add iptables chain(s) to create
	-s SCRIPT	Additional script to run
	-e		Diagnostic mode - echo commands
EOF
}

while getopts ":hf:l:n:pb:r:N:eb:r:Rc:i:4:6:q:Q:I:s:" opt; do
	case $opt in
	h)
		show_help
		exit 0
		;;
	e)
		ECHO=echo
		;;
	f)
		LOW=$OPTARG
		;;
	l)
		HIGH=$OPTARG
		;;
	p)
		FIXED_LEN=1
		;;
	n)
		NS_LIST=$OPTARG
		;;
	b)
		BRIDGE_NS=$OPTARG
		[[ -z $BRIDGE_NS ]] && echo Bridge namespace cannot be blank && exit 1
		;;
	r)
		ROOT_NS=$OPTARG
		;;
	R)
		ROOT_NS=
		;;
	N)
		SIDE=$OPTARG
		;;
	c)
		BR_NAME=$OPTARG
		;;
	i)
		IF_NAME=$OPTARG
		;;
	4)
		IP_BASE_ADDR=$OPTARG
		;;
	6)
		IP6_BASE_ADDR=$OPTARG
		;;
	q)
		ROUTES="$ROUTES $OPTARG"
		;;
	Q)
		ROUTES="$ROUTES @$OPTARG"
		;;
	I)
		CHAINS="$CHAINS $OPTARG"
		;;
	s)
		SCRIPT=$OPTARG
		;;
	?)
		echo Unknown option -$OPTARG
		show_help
		exit 1
		;;
	:)
		echo Missing argument -$OPTARG
		show_help
		exit 1
		;;
	esac
done

CMD=${!OPTIND}

# Work out number length of interface names
if [[ $FIXED_LEN -eq 1 ]]; then
	NUM_LEN=1
	LAST=$HIGH
	while [[ $(( LAST /= 10 )) -gt 0 ]]; do
		: $((NUM_LEN++))
	done
fi

[[ $IF_NAME != eth ]] && BR_NAME=br${IF_NAME:2}

# Check if system is using iptables or nftables
USE_IPTABLES=1
type -f iptables >/dev/null
if [[ $? -ne 0 ]]; then
	USE_IPTABLES=0
else
	iptables -V | grep -q "nf_*t"
	[[ $? -eq 0 ]] && USE_IPTABLES=0
fi

if [[ -z $NS_LIST ]]; then
	[[ -z $BRIDGE_NS ]] && BRIDGE_NS=medium
	NS_LIST="high low $BRIDGE_NS $ROOT_NS"

	ip=([high]=1 [low]=3 [$BRIDGE_NS]=2 [${ROOT_NS:-none}]=254)
else
	if [[ -z $BRIDGE_NS ]]; then
		set $NS_LIST
		BRIDGE_NS=$1
	else
		[[ ! " $NS_LIST " =~ " $BRIDGE_NS " ]] && NS_LIST="$NS_LIST $BRIDGE_NS"
		[[ -n $ROOT_NS && ! " $NS_LIST " =~ " $ROOT_NS " ]] && NS_LIST="$NS_LIST $ROOT_NS"
	fi

	set $NS_LIST
	for IP in $(seq 1 $#); do
		ip[$1]=$IP
		shift
	done
fi

[[ -z $CHAINS ]] && CHAINS=$DEFAULT_CHAINS

if [[ $CMD = add ]]; then
	for n in $NS_LIST; do
		[[ .$n = .$ROOT_NS ]] && continue
		ip netns | grep $n >/dev/null 2>/dev/null
		[[ $? -eq 0 ]] && continue

		$ECHO unshare -n ip netns add $n
		$ECHO ip netns exec $n ip link set up lo

		NEW_NS+=" $n"
	done
fi

if [[ $USE_IPTABLES -eq 1 ]]; then
	for n in $NS_LIST; do
		[[ $CMD = del ]] && IPT_CMD=X || IPT_CMD=N

		[[ $n = $ROOT_NS ]] && NETNS_CMD= || NETNS_CMD="ip netns exec $n"
		for v in "" 6; do
			for c in $CHAINS; do
				$ECHO $NETNS_CMD ip${v}tables -$IPT_CMD $c
			done
		done
	done
fi

for n in $(seq $LOW $HIGH); do
	if [[ -n $NUM_LEN ]]; then
		IF=${IF_NAME}$(printf "%${NUM_LEN}.${NUM_LEN}d" $n)
		BR=${BR_NAME}$(printf "%${NUM_LEN}.${NUM_LEN}d" $n)
	else
		IF=${IF_NAME}$n
		BR=${BR_NAME}$n
	fi

	if [[ $CMD = add || $CMD = remake ]]; then
		if [[ $CMD = add ]]; then
			$ECHO ip netns exec $BRIDGE_NS ip link add ${BR} type bridge
			$ECHO ip netns exec $BRIDGE_NS ip link set ${BR} up
			$ECHO ip netns exec $BRIDGE_NS ip addr add ${IP_BASE_ADDR}.${n}.${ip[medium]}/24 broadcast + dev ${BR}
			$ECHO ip netns exec $BRIDGE_NS ip addr add ${IP6_BASE_ADDR}:$((100 + $n))::${ip[medium]}/64 dev ${BR}
			NS=$NS_LIST
		else
			NS=$SIDE
		fi

		for p in $NS; do
			[[ $p = $BRIDGE_NS ]] && continue

			$ECHO ip netns exec $BRIDGE_NS ip link add ${IF}.$p type veth peer name ${IF}
			$ECHO ip netns exec $BRIDGE_NS ip link set ${IF}.$p master ${BR}
			$ECHO ip netns exec $BRIDGE_NS ip link set ${IF}.$p up

			$ECHO ip netns exec $BRIDGE_NS ip link set ${IF} netns ${p/$ROOT_NS/1}

			[[ $p = $ROOT_NS ]] && NETNS_CMD= || NETNS_CMD="ip netns exec $p"
			$ECHO $NETNS_CMD ip link set ${IF} up
			$ECHO $NETNS_CMD ip addr add ${IP_BASE_ADDR}.${n}.${ip[$p]}/24 broadcast + dev ${IF}
			$ECHO $NETNS_CMD ip addr add ${IP6_BASE_ADDR}:$((100 + $n))::${ip[$p]}/64 dev ${IF}
		done

		$ECHO ip netns exec $BRIDGE_NS ip link add ${IF} type dummy
		$ECHO ip netns exec $BRIDGE_NS ip link set ${IF} master ${BR}
		$ECHO ip netns exec $BRIDGE_NS ip link set up ${IF}
	elif [[ $CMD = del ]]; then
		for p in $NS_LIST; do
			[[ $p = $BRIDGE_NS ]] && continue

			$ECHO ip netns exec $BRIDGE_NS ip link del ${IF}.$p
		done
		$ECHO ip netns exec $BRIDGE_NS ip link del ${IF}
		$ECHO ip netns exec $BRIDGE_NS ip link del ${BR}
	else
		echo Unknown command $CMD
	fi
done

# We have already deleted the interfaces, so the routes will have been deleted
if [[ -n $ROUTES && -n $ROOT_NS  && $CMD != del ]]; then
	for n in $NS_LIST; do
		[[ $n = $ROOT_NS ]] && continue

		[[ $n = $BRIDGE_NS ]] && IFN=$BR_NAME || IFN=$IF_NAME
		for r in $ROUTES; do
			set ${r/@/ }
			[[ $# -eq 1 ]] && set "" $1

			[[ $1 =~ : ]] && VIA=${IP6_BASE_ADDR}:$((100 + $2))::${ip[$ROOT_NS]} || VIA=${IP_BASE_ADDR}.$2.${ip[$ROOT_NS]}
			$ECHO ip netns exec $n ip route add ${1:-default} via $VIA dev ${IFN}$2
			[[ -z $1 ]] && $ECHO ip netns exec $n ip -6 route add default via ${IP6_BASE_ADDR}:$((100 + $2))::${ip[$ROOT_NS]} dev ${IFN}$2
		done
	done
fi

if [[ $CMD = del ]]; then
	for n in $NS_LIST; do
		[[ $n = $ROOT_NS ]] && continue
		$ECHO ip netns del $n
	done
fi

[[ -n $SCRIPT ]] && $SCRIPT $CMD $IP_BASE_ADDR $IP6_BASE_ADDR $IF_NAME $ECHO
