#!/usr/bin/ksh
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source.  A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.

#
# Copyright 2019 Joyent, Inc.
# Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
#

#
# Functions shared across the network tests.
#

DEBUG=0

function dbg
{
	typeset msg="$*"
	if (($DEBUG == 1)); then
		echo "DBG [$nt_tname]: $msg"
	fi
}

function fail
{
	typeset msg="$*"
	echo "FAIL [$nt_tname]: $msg" >&2
	exit 1
}

function maybe_fail
{
	typeset msg=$1

	if ((BAIL == 1)); then
		fail "$msg"
	else
		dbg "$msg"
		return 1
	fi
}

function zone_exists
{
	typeset name=$1

	if (($# != 1)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "checking for existence of zone: $name"
	if zoneadm -z $name list > /dev/null 2>&1; then
		dbg "found zone: $name"
		return 0
	else
		dbg "zone not found: $name"
		return 1
	fi
}

function zone_running
{
	typeset name=$1
	typeset state=$(zoneadm -z $name list -p | awk -F: '{ print $3 }')
	typeset err="zone $name is not running"

	if (($# != 1)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "check if zone $name is running"
	dbg "state of zone $name: $state"
	if [[ "$state" == "running" ]]; then
		dbg "zone $name is running"
		return 0
	fi

	maybe_fail "$err"
}

function simnet_exists
{
	typeset name=$1

	if (($# != 1)); then
		fail "$0: incorrect number of args provided"
	fi

	if dladm show-simnet $name > /dev/null 2>&1; then
		dbg "simnet $name found"
		return 0
	else
		dbg "simnet $name not found"
		return 1
	fi
}

function create_simnet
{
	typeset name=$1
	typeset err="failed to create simnet $name"

	if (($# != 1)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "creating simnet $name"
	if simnet_exists $name; then
		dbg "simnet $name already exists"
		maybe_fail "$err"
		return 1
	fi

	if dladm create-simnet > /dev/null $name; then
		dbg "created simnet $name"
		return 0
	fi

	maybe_fail "$err"
}

function delete_simnet
{
	typeset name=$1
	typeset err="failed to delete simnet $name"

	if (($# != 1)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "deleting simnet $name"
	if ! simnet_exists $name; then
		dbg "simnet $name doesn't exist"
		return 1
	fi

	if dladm delete-simnet $name; then
		dbg "simnet $name deleted"
		return 0
	fi

	maybe_fail "$err"
}

function link_simnets
{
	typeset sim1=$1
	typeset sim2=$2
	typeset err="failed to link simnet $sim1 to $sim2"

	if (($# != 2)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "linking simnet $sim1 to $sim2"
	if dladm modify-simnet -p $sim2 $sim1 > /dev/null; then
		dbg "linked simnet $sim1 to $sim2"
		return 0
	fi

	maybe_fail "$err"
}

function vnic_exists
{
	typeset name=$1
	typeset vid=$2
	typeset over=$3
	typeset zone=$4

	if (($# != 4)); then
		fail "$0: incorrect number of args provided"
	fi

	if dladm show-vnic $name > /dev/null 2>&1; then
		typeset avid=$(dladm show-vnic -p -o vid $name)
		typeset aover=$(dladm show-vnic -p -o over $name)
		typeset azone=$(dladm show-linkprop -cp zone -o value $name)
		if (($avid == $vid)) && [ $aover == $over ] && \
			   [ $azone == $zone ]
		then
			return 0
		else
			return 1
		fi
	else
		return 1
	fi
}

function create_vnic
{
	typeset name=$1
	typeset over=$2
	typeset vid=$3
	typeset zone=$4
	typeset r=1
	typeset vid_opt=""
	typeset vnic_info="$name, vid: $vid, over: $over, zone: $zone"
	typeset err="failed to create VNIC: $vnic_info"

	if (($# != 4)); then
		fail "$0: incorrect number of args provided"
	fi

	if ((vid != 0)); then
		vid_opt="-v $vid"
	fi

	dbg "creating VNIC: $vnic_info"
	if dladm create-vnic -l $over $vid_opt $name > /dev/null 2>&1 && \
	    dladm set-linkprop -t -p zone=$zone $name > /dev/null 2>&1
	then
		dbg "created VNIC: $vnic_info"
		return 0
	fi

	maybe_fail "$err"
}

function delete_vnic
{
	typeset name=$1
	typeset vid=$2
	typeset zone=$3
	typeset vnic_info="$name, vid: $vid, zone: $zone"
	typeset err1="failed to assign VNIC $name from $zone to GZ"
	typeset err2="failed to delete VNIC: $vnic_info"

	if (($# != 3)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "assigning VNIC $name from $zone to GZ"
	if ! dladm set-linkprop -t -p zone=global $name; then
		maybe_fail "$err1"
		return 1
	fi

	dbg "deleting VNIC: $vnic_info"
	if dladm delete-vnic $name > /dev/null; then
		dbg "deleted VNIC: $vnic_info"
		return 0
	fi

	maybe_fail "$err2"
}

function create_addr
{
	typeset zone=$1
	typeset vnic=$2
	typeset ip=$3
	typeset ipname=${vnic}/v4

	if (($# != 3)); then
		fail "$0: incorrect number of args provided"
	fi

	if zlogin $zone ipadm create-addr -t -T static -a $ip \
		  $ipname > /dev/null
	then
		dbg "created addr $ipname ($ip) in zone $zone"
		return 0
	fi

	maybe_fail "failed to create addr $ipname ($ip) in zone $zone"
}

function create_addr6
{
	typeset zone=$1
	typeset vnic=$2
	typeset ip=$3
	typeset ll_name=${vnic}/v6
	typeset uni_name=${vnic}/v6add
	typeset err1="failed to create link-local addr $ll_name in zone $zone"
	typeset err2="failed to create unicast addr $uni_name in zone $zone"

	if (($# != 3)); then
		fail "$0: incorrect number of args provided"
	fi

	if zlogin $zone ipadm create-addr -t -T addrconf $ll_name; then
		dbg "created link-local addr $ll_name in zone $zone"
	else
		maybe_fail "$err1"
		return 1
	fi

	if zlogin $zone ipadm create-addr -t -T static -a $ip/64 $uni_name; then
		dbg "created unicast addr $uni_name in zone $zone"
	else
		maybe_fail "$err2"
	fi
}

function delete_addr
{
	typeset zone=$1
	typeset ifname=$2
	typeset version=$3
	typeset ipname=$ifname/$version

	if (($# != 3)); then
		fail "$0: incorrect number of args provided"
	fi

	if zlogin $zone ipadm show-addr $ipname > /dev/null 2>&1; then
		if zlogin $zone ipadm delete-addr $ipname > /dev/null; then
			dbg "deleted addr $ipname in zone $zone"
		else
			maybe_fail "failed to delete addr $ipname in zone $zone"
			return 1
		fi
	else
		dbg "addr $ipname doesn't exist in zone $zone"
	fi

	if [[ "v6" == "$version" ]]; then
		typeset ipname=$ifname/v6add
		typeset err="failed to delete addr $ipname in zone $zone"

		if zlogin $zone ipadm show-addr $ipname > /dev/null 2>&1; then
			if zlogin $zone ipadm delete-addr $ipname > /dev/null
			then
				dbg "deleted addr $ipname in zone $zone"
			else
				maybe_fail "$err"
			fi
		else
			dbg "addr $ipname doesn't exist in zone $zone"
		fi
	fi
}

function delete_if
{
	typeset zone=$1
	typeset ifname=$2
	typeset err="failed to delete interface $ifname in zone $zone"

	if (($# != 2)); then
		fail "$0: incorrect number of args provided"
	fi

	if zlogin $zone ipadm show-if $ifname > /dev/null 2>&1; then
		if zlogin $zone ipadm delete-if $ifname > /dev/null; then
			dbg "deleted interface $ifname in zone $zone"
		else
			maybe_fail "$err"
		fi
	else
		dbg "interface $ifname doesn't exist in zone $zone"
	fi
}

function ip_fwd_enable
{
	typeset zone=$1

	if (($# != 1)); then
		fail "$0: incorrect number of args provided"
	fi

	if zlogin $zone routeadm -p ipv4-forwarding | \
			egrep 'current=enabled' > /dev/null
	then
		dbg "IPv4 forwarding already enabled for $zone"
	else
		if zlogin $zone routeadm -ue ipv4-forwarding; then
			dbg "enabled IPv4 forwarding for $zone"
		else
			maybe_fail "failed to enable IPv4 forwarding for $zone"
			return 1
		fi
	fi

	if zlogin $zone routeadm -p ipv6-forwarding | \
			egrep 'current=enabled' > /dev/null
	then
		dbg "IPv6 forwarding already enabled for $zone"
	else
		if zlogin $zone routeadm -ue ipv6-forwarding; then
			dbg "enabled IPv6 forwarding for $zone"
		else
			maybe_fail "failed to enable IPv6 forwarding for $zone"
		fi
	fi
}

function ip_fwd_disable
{
	typeset zone=$1

	if (($# != 1)); then
		fail "$0: incorrect number of args provided"
	fi

	if zlogin $zone routeadm -p ipv4-forwarding | \
			egrep 'current=disabled' > /dev/null
	then
		dbg "IPv4 forwarding already disabled for $zone"
	else
		if zlogin $zone routeadm -ud ipv4-forwarding; then
			dbg "disabled IPv4 forwarding in $zone"
		else
			maybe_fail "failed to disable IPv4 forwarding in $zone"
			return 1
		fi
	fi

	if zlogin $zone routeadm -p ipv6-forwarding | \
			egrep 'current=disabled' > /dev/null
	then
		dbg "IPv6 forwarding already disabled for $zone"
	else
		if zlogin $zone routeadm -ud ipv6-forwarding; then
			dbg "disabled IPv6 forwarding in $zone"
		else
			maybe_fail "failed to disable IPv6 forwarding in $zone"
		fi
	fi
}

function add_route
{
	typeset zone=$1
	typeset dest=$2
	typeset net=$3
	typeset gateway=$4

	if (($# != 4)); then
		fail "$0: incorrect number of args provided"
	fi

	if zlogin $zone route -n add $net $gateway > /dev/null; then
		dbg "added route $gateway => $net to $zone"
		return 0
	fi

	maybe_fail "failed to add route $gateway => $net to $zone"
}

function add_route6
{
	typeset zone=$1
	typeset dest=$2
	typeset net=$3
	typeset gateway=$4

	if (($# != 4)); then
		fail "$0: incorrect number of args provided"
	fi

	if zlogin $zone route -n add -inet6 $net $gateway > /dev/null
	then
		dbg "added route $gateway => $net to $zone"
		return 0
	fi

	maybe_fail "failed to add route $gateway => $net to $zone"
}

function rm_route
{
	typeset zone=$1
	typeset dest=$2
	typeset net=$3
	typeset gateway=$4
	typeset gw=$(zlogin $zone route -n get $dest | \
			     grep gateway | awk '{ print $2 }')
	typeset err="failed to remove route $gateway => $net from $zone"

	if (($# != 4)); then
		fail "$0: incorrect number of args provided"
	fi

	if [[ "$gw" == "$gateway" ]]; then
		if zlogin $zone route -n delete $net $gateway > /dev/null
		then
			dbg "removed route $gateway => $net from $zone"
		else
			maybe_fail "$err"
		fi
	else
		dbg "$zone already lacked route $gateway => $net"
	fi
}

function rm_route6
{
	typeset zone=$1
	typeset dest=$2
	typeset net=$3
	typeset gateway=$4
	typeset gw=$(zlogin $zone route -n get -inet6 $dest | \
			     grep gateway | awk '{ print $2 }')
	typeset err="failed to remove route $gateway => $net from $zone"

	if (($# != 4)); then
		fail "$0: incorrect number of args provided"
	fi

	if [[ "$gw" == "$gateway" ]]; then
		if zlogin $zone route -n delete -inet6 $net $gateway > /dev/null
		then
			dbg "removed route $gateway => $net from $zone"
		else
			maybe_fail "$err"
		fi
	else
		dbg "$zone already lacked route $gateway => $net"
	fi
}

function set_linkprop
{
	typeset link=$1
	typeset prop=$2
	typeset val=$3
	typeset err="failed to set $link prop: $prop=$val"

	if (($# != 3)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "attempt to set $link prop: $prop=$val"
	if dladm set-linkprop -p $prop=$val $link; then
		dbg "set $link prop: $prop=$val"
		return 0
	fi

	maybe_fail "$err"
}

function ping
{
	typeset zone=$1
	typeset src=$2
	typeset dst=$3
	typeset info="$src -> $dst"

	if (($# != 3)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "ping: $info"
	if zlogin $zone ping $dst > /dev/null 2>&1; then
		dbg "successful ping: $info"
		return 0
	fi

	maybe_fail "could not ping: $info"
}

function ping_udp
{
	typeset client=$1
	typeset client_ip=$2
	typeset server_ip=$3
	typeset size=$4
	typeset num=$5
	typeset info="$client_ip -> $server_ip (size: $size)"

	if (($# != 5)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "UDP ping: $info"
	if zlogin $client ping -ns -U $server_ip $size $num > /dev/null; then
		dbg "UDP ping passed: $info"
		return 0
	fi

	maybe_fail "UDP ping failed: $info"
}

function start_server
{
	typeset zone=$1
	typeset type=$2
	typeset ip=$3
	typeset port=$4
	typeset ofile=$5

	if (($# != 5)); then
		fail "$0: incorrect number of args provided"
	fi

	dbg "start server $rfile"
	zlogin $zone \
	       /usr/bin/socat -u ${type}-LISTEN:$port,bind=[$ip],reuseaddr \
	       CREATE:$ofile &
	listener_ppid=$!
	dbg "listener PPID: $listener_ppid, zone $zone"
}

function wait_for_pid
{
	typeset pid=$1
	typeset seconds=$2
	typeset s=0

	if (($# != 2)); then
		fail "$0: incorrect number of args provided"
	fi

	while true; do
		if kill -0 $pid > /dev/null 2>&1; then
			if ((seconds == s)); then
				maybe_fail "timed out waiting for pid $pid"
				return 1
			fi
			dbg "waiting for pid $pid"
			sleep 1
			((s++))
		else
			return 0
		fi
	done
}
