#!/bin/bash
#
# VRF admin script
#
# This script is mostly a wrapper around ip and cgroups commands used to
# create and configure VRFs. The intent is to provide a better and cleaner
# user experience for managing VRFs on Linux. Perhaps one day this becomes
# a subcommand of ip (ip vrf ...)

PROG=${0##*/}

CFGDIR=/var/lib/vrf

VERBOSE=0

# metric for unreachable default routes added to each vrf table
# - static routes for IPv6 default to a metric of 1024 (IP6_RT_PRIO_USER)
#   so our unreachable default should be below that
RT_METRIC=8192

# Allowed table id range
TBID_MIN=1001
TBID_MAX=1255

################################################################################
# utilities

function log_cmd
{
	if [ $VERBOSE -eq 1 ]; then
		echo "${PROG}: $*"
	fi
}

function dev_exists
{
	ip link show dev ${1} >/dev/null 2>&1
}


# return device index for given device name

function dev_index
{
	local dev=$1
	local idx

	idx=$(ip -o link show dev ${dev} 2>/dev/null | awk '{print $1}')
	idx=${idx/:}
	echo $idx

	[ -z "$idx" ] && return 1

	return 0
}

# Look up master device for a link

function dev_get_master
{
	local dev=$1

	ip -o link show dev $dev | awk '{
	for (i = 0; i < NF; ++i) {
		if ($i == "master") {
			j=i+1
			print $j
			i=NF
		}
	}
	}'
}

function get_comm
{
	awk '$1 == "Name:" {print $2}' /proc/$1/status
}

function normalize_vrf_name
{
	local vrf=$1

	[ -z "$vrf" ] && return 1

	case "$vrf" in
		*vrf*) name=$vrf;;
		*) name=${vrf}-vrf;;
	esac

	echo $name
	return 0
}

# VRF device name should only contain alphanumeric characters
function get_vrf_arg
{
	local vrf

	vrf="${1//[^a-zA-Z0-9_-.]/}"
	if [ "$vrf" != "$1" ]; then
		echo "Invalid VRF"
		return 1
	fi

	echo $vrf
}

# table id should only contain digits
function get_tbid_arg
{
	local tbid

	tbid="${1//[^0-9]/}"
	if [ "$tbid" != "$1" ]; then
		echo "Invalid table id"
		return 1
	fi

	echo $tbid
}

################################################################################
# cgroup functions

function l3mdev_cgroup_exists
{
	if [ -e /sys/fs/cgroup/l3mdev ];
	then
		return 0
	fi

	echo
	echo "Cgroup for managing VRF context does not exist."
	echo "Has l3mdev cgroup patch been applied to kernel?"
	echo "If so has it been enabled?"
	echo

	return 1
}

function vrf_cgroup_exists
{
	local vrf=$1

	[ -e /sys/fs/cgroup/l3mdev/${vrf} ] && return 0

	echo "cgroup does not exist for VRF."

	return 1
}

# return l3mdev cgroup task is associated with
function vrf_get_cgroup
{
	local p=$1
	local c

	c=$(grep l3mdev /proc/$p/cgroup | awk -F':' '{print $NF}')
	# strip leading '/'; makes default VRF ""
	echo ${c/\/}
}

# configure cgroup for VRF
function vrf_configure_cgroup
{
	local vrf=$1

	l3mdev_cgroup_exists
	[ $? -ne 0 ] && return 0

	log_cmd "cgcreate -g l3mdev:/${vrf}"
	cgcreate -g l3mdev:/${vrf} || return 1

	log_cmd "cgset -r l3mdev.master-device=${vrf} ${vrf}"
	cgset -r l3mdev.master-device=${vrf} ${vrf}
}

function vrf_delete_cgroup
{
	log_cmd "cgdelete -g l3mdev:/${1}"
	cgdelete -g l3mdev:/${1}
}

################################################################################
# handle systemd-based services

function systemd_start_vrf
{
	local vrf=${1}

	systemctl --no-block list-units  *.service | \
	    awk '$1 ~ /@.*.service$/ {print $1}' | \
	    grep "@${vrf}" | \
	while read s
	do
		systemctl --no-block start ${s}
	done
}

function systemd_stop_vrf
{
	local vrf=${1}

	systemctl --no-block list-units  *.service | \
	    awk '$1 ~ /@.*.service$/ {print $1}' | \
	    grep "@${vrf}" | \
	while read s
	do
		systemctl --no-block stop ${s}
	done
}

################################################################################
# vrf device functions

function vrf_exists
{
	local vrf=${1}

	[ "$vrf" = "default" ] && return 0

	ip link show dev ${vrf} type vrf >/dev/null 2>&1
	return $?
}

# get table id for vrf device
function vrf_get_table
{
	ip -o -d link show dev ${1} 2>/dev/null | sed -e 's/.*vrf table \([0-9]*\) .*/\1/'
}

function vrf_table
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	vrf_exists $vrf
	if [ $? -ne 0 ]; then
		echo "VRF does not exist"
		return 1
	fi

	vrf_get_table $vrf
}

# return list of VRFs that have been created
function vrf_get_list
{
	ip -br link show type vrf | awk '{print $1}'
}

function vrf_add_rules
{
	local vrf=$1
	local tbid=$2

	# setup fib rules which direct lookups to the proper table
	ip ru add pref 200 oif ${vrf} table ${tbid} || return 1
	ip ru add pref 200 iif ${vrf} table ${tbid} || return 1
	ip -6 ru add pref 200 oif ${vrf} table ${tbid} || return 1
	ip -6 ru add pref 200 iif ${vrf} table ${tbid} || return 1

	return 0
}

function vrf_delete_rules
{
	local vrf=$1

	ip ru delete oif ${vrf}
	ip ru delete iif ${vrf}
	ip -6 ru delete oif ${vrf}
	ip -6 ru delete iif ${vrf}

	return 0
}

# setup default route for vrf - use a very poor metric so other
# default routes will take precedence. We need a default route
# to keep the lookups from dropping from one table to the next
function vrf_add_default_route
{
	local tbid=$1

	log_cmd "ip route add table ${tbid} unreachable default metric $RT_METRIC"
	ip route add table ${tbid} unreachable default metric $RT_METRIC || return 1

	log_cmd "ip -6 route add table ${tbid} unreachable default metric $RT_METRIC"
	ip -6 route add table ${tbid} unreachable default metric $RT_METRIC || return 1
}

function vrf_remove_default_route
{
	local tbid=$1

	log_cmd "ip route delete table ${tbid} unreachable default metric $RT_METRIC"
	ip route delete table ${tbid} unreachable default metric $RT_METRIC >/dev/null 2>&1

	log_cmd "ip -6 route delete table ${tbid} unreachable default metric $RT_METRIC"
	ip -6 route delete table ${tbid} unreachable default metric $RT_METRIC >/dev/null 2>&1
}

function vrf_verify_default_route
{
	local tbid=$1
	local rc=0

	# really want to check metric
	ip route ls table ${tbid} | grep -q "unreachable default "
	if [ $? -ne 0 ]; then
		echo "    ERROR: IPv4 default route missing"
		rc=1
	fi

	ip -6 route ls table ${tbid} | grep -q "unreachable default "
	if [ $? -ne 0 ]; then
		echo "    ERROR: IPv6 default route missing"
		rc=1
	fi

	if [ $rc -eq 0 ]; then
		echo "    default routes are installed"
	fi

	return $rc
}

# Configure a VRF
function vrf_configure
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	local tbid=$(get_tbid_arg ${2})
	[ $? -ne 0 ] && return 1

	local mode=$3

	# vrf_configure can be called directly; make sure args exist
	if [ -z "$tbid" ]; then
		usage
		return 1
	fi

	if [ -n "$mode" -a "$mode" != "boot" ]; then
		usage
		return 1
	fi

	# only rebuild the VRF if it is not configured properly
	verify_vrf_one ${vrf} ${tbid} "yes" >/dev/null 2>&1
	[ $? -eq 0 ] && return 0

	# make sure old remnants of prior vrf are removed
	vrf_teardown ${vrf} ${tbid} 2>/dev/null

	vrf_add_default_route ${tbid}
	if [ $? -ne 0 ]; then
		echo "Failed to install default routes"
		return 1
	fi

	vrf_configure_cgroup ${vrf}
	if [ $? -ne 0 ]; then
		echo "Failed to configure cgroup"
		return 1
	fi

	# restart any systemd processes bound to VRF
	if [ -z "${mode}" ]; then
		systemd_stop_vrf ${vrf}
		systemd_start_vrf ${vrf}
	fi

	return 0
}

# Remove all configuration for VRF
function vrf_teardown
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	local tbid=$(get_tbid_arg ${2})
	[ $? -ne 0 ] && return 1

	systemd_stop_vrf ${vrf}

	if [ -n "$tbid" ]; then
		vrf_remove_default_route ${tbid}
	fi

	vrf_delete_cgroup ${vrf}
}

function vrf_list
{
	local tbid
	local vrf

	if [ -n "$1" ]; then
		usage
		return 1
	fi

	printf "\n"
	printf "%-16s %-5s\n" "VRF" "Table"
	printf "%-16s %-5s\n" "----------------" "-----"

	vrf=$(vrf_get_list)
	for v in $vrf
	do
		tbid=$(vrf_get_table $v)
		printf "%-16s %5s\n" $v $tbid
	done

	echo

	return 0
}

# Unsupported option; for testing and tooling only
function vrf_add
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	local tbid=$(get_tbid_arg ${3})
	[ $? -ne 0 ] && return 1

	if [ "$2" != "table" -o -z "$3" -o -n "$4" ];
	then
		usage
		return 1
	fi

	if [ "${vrf}" = "default" ]; then
		echo "'default' is a reserved VRF name"
		return 1
	fi

	vrf_exists ${vrf}
	if [ $? -eq 0 ]; then
		echo "VRF already exists"
		return 1
	fi

	if [ ${tbid} -lt ${TBID_MIN} -o ${tbid} -gt ${TBID_MAX} ]; then
		echo "Invalid table id. Must be between ${TBID_MIN} and ${TBID_MAX}"
		return 1
	fi

	# create device
	ip link add ${vrf} type vrf table ${tbid}

	# echo "${tbid}  ${vrf}" > /etc/iproute2/rt_tables.d/${vrf}.conf

	vrf_add_rules ${vrf} ${tbid}

	vrf_configure ${vrf} ${tbid}

	ip link set dev ${vrf} up
}

# Unsupported option; for testing and tooling only
function vrf_delete
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	local tbid=$(get_tbid_arg ${2})
	[ $? -ne 0 ] && return 1

	if [ -z "$vrf" ];
	then
		usage
		return 1
	fi

	if [ "${vrf}" = "default" ]; then
		echo "Can not delete the default VRF"
		return 1
	fi

	[ -z "$tbid" ] && tbid=$(vrf_get_table $vrf)
	# rm -f /etc/iproute2/rt_tables.d/${vrf}.conf

	ip link delete dev ${vrf} 2>/dev/null

	vrf_delete_rules ${vrf}

	vrf_teardown ${vrf} ${tbid} 2>/dev/null
}

################################################################################
# link management

function link_usage
{
	cat <<EOF

Links associated with VRF domains:
    $PROG link list [<vrf-name>]
EOF
    # Unsupported options for customers; tooling and automated testing only
    # $PROG link add <vrf-name> dev <dev-name>
    # $PROG link remove dev <dev-name>
}

function link_list_vrf
{
	local vrf=${1}

	dev_exists $vrf
	if [ $? -ne 0 ]; then
		echo "VRF does not exist"
		return 1
	fi

	printf "\nVRF: %-16s\n" $vrf
	echo "--------------------"

	ip -br link show master ${vrf}
}

function link_list
{
	local vrf="$*"

	if [ -z "$vrf" ]; then
		vrf=$(vrf_get_list)
	fi

	for v in $vrf
	do
		link_list_vrf ${v}
	done

	echo
}

function link_add
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	local dev=${3}

	if [ "$2" != "dev" -o -z "$3" -o -n "$4" ];
	then
		link_usage
		return 1
	fi

	dev_exists $vrf
	if [ $? -ne 0 ]; then
		echo "VRF does not exist"
		return 1
	fi

	dev_exists $dev
	if [ $? -ne 0 ]; then
		echo "dev does not exist"
		return 1
	fi

	ip link set dev $dev master ${vrf}
}

function link_remove
{
	local dev=$2

	if [ "$1" != "dev" -o -z "$2" -o -n "$3" ];
	then
		link_usage
		return 1
	fi

	ip -o -d link show ${dev} 2>/dev/null | grep -q vrf_slave
	if [ $? -ne 0 ]; then
		echo "dev is not a VRF slave"
		return 1
	fi

	ip link set dev $dev nomaster
}

function link_cmd
{
	local cmd=$1
	shift

	case "$cmd" in
		ls|list|sh|show) link_list   $*;;

		####################################################
		# these options are for testing and tooling; they
		# are not intended for use by customers
		a|add)        link_add    $*;;
		r|rem|remove) link_remove $*;;
		####################################################

		help)         link_usage;;
		*)            link_usage; return 1;;
	esac
}

################################################################################
# route management

function route_usage
{
cat <<EOF

Routes for a VRF domain:
    $PROG route list [<vrf-name>]
EOF
    # Unsupported options for customers; tooling and automated testing only
    # $PROG route {add|delete} <vrf-name> <route spec>
    # $PROG route get <vrf-name> <host>
}

function route_show_vrf
{
	local vrf=${1}

	dev_exists $vrf
	if [ $? -ne 0 ]; then
		echo "VRF does not exist"
		return 1
	fi

	local tbid=$(vrf_get_table $vrf)

	printf "\nVRF: %-16s\n" $vrf
	echo "--------------------"

	ip route show table $tbid
	echo
	ip -6 route show table $tbid
}

function route_show
{
	local vrf="$*"

	if [ -z "$vrf" ];
	then
		vrf=$(vrf_get_list)
	fi

	for v in ${vrf}
	do
		route_show_vrf ${v}
	done

	echo
}

function route_add
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	shift

	dev_exists $vrf
	if [ $? -ne 0 ]; then
		echo "VRF does not exist"
		return 1
	fi
	local tbid=$(vrf_get_table $vrf)

	ip route add table $tbid $*
}

function route_delete
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	shift

	dev_exists $vrf
	if [ $? -ne 0 ]; then
		echo "VRF does not exist"
		return 1
	fi
	local tbid=$(vrf_get_table $vrf)

	ip route del table $tbid $*
}

function route_get
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	shift

	dev_exists $vrf
	if [ $? -ne 0 ]; then
		echo "VRF does not exist"
		return 1
	fi

	ip route get oif $vrf $*
}

function route_cmd
{
	local cmd=$1
	shift

	case "$cmd" in
		ls|list|sh|show)  route_show $*;;
		get)          route_get $*;;

		####################################################
		# these options are for testing and tooling; they
		# are not intended for use by customers
		a|add)        route_add $*;;
		d|del|delete) route_delete $*;;
		####################################################

		help)         route_usage;;
		*)            route_usage; return 1;;
	esac
}


################################################################################
# task management

function task_usage
{
	cat <<EOF

Tasks and VRF domain asociation:
    $PROG task exec <vrf-name> <command>
    $PROG task list [<vrf-name>]
    $PROG task identify <pid>

    NOTE: This command affects only AF_INET and AF_INET6 sockets opened by the
          command that gets exec'ed. Specifically, it has *no* impact on netlink
          sockets (e.g., ip command).
EOF
}

function task_list_vrf
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	printf "\nVRF: %-16s\n" $vrf
	echo "-----------------------"

	local f="/sys/fs/cgroup/l3mdev/${vrf}/cgroup.procs"
	if [ ! -e $f ]; then
		echo "No cgroup for vrf"
	else
		while read p
		do
			c=$(get_comm $p)
			printf "%-16s  %5s\n" $c $p
		done < $f
	fi
}

# show VRF for specific task
function task_identify
{
	local do_prompt=0

	if [ "$1" = "prompt" ]; then
		do_prompt=1
		shift
	fi

	# allow syntax to be ... pid <pid>
	[ "$1" = "pid" ] && shift
	pid=$*

	# default to parent PID (not this commands PID)
	[ -z "$pid" ] && pid=$PPID

	c=$(vrf_get_cgroup $pid)
	if [ -n "$c" ]; then
		if [ $do_prompt = "1" ]; then
			c=$(normalize_vrf_name $c)
			echo ":$c"
		else
			echo "$c"
		fi
	elif [ $do_prompt != "1" ]; then
		echo "default"
	fi
}

function task_list
{
	local vrf

	# show all tasks in 1 or more VRFs
	vrf="$*"
	if [ -z "$vrf" ]; then
		vrf=$(vrf_get_list)
	fi

	for v in ${vrf}
	do
		task_list_vrf ${v}
	done

	echo
}

# This should only be used by experienced users and known contexts.
# Changing the context of a running task only affects future ipv4 and
# ipv6 sockets. This option is used by various OS scripts to set
# the context of a parent process before exec'ing another process
# - a proper example of how this should be used.

function task_set
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	local pid=${2//[^0-9]/}

	if [ "$pid" != "$2" ]; then
		echo "Invalid process id"
		return 1
	fi
	if [ -n "$3" ]; then
		task_usage
		return 1
	fi

	vrf_exists $vrf
	if [ $? -ne 0 ]; then
		echo "VRF does not exist"
		return 1
	fi
	[ -z "$pid" ] && pid=$PPID

	kill -0 $pid >/dev/null 2>&1
	if [ $? -ne 0 ]; then
		echo "Process does not exist"
		return 1
	fi

	if [ "$vrf" = "default" ]; then
		echo $pid >> /sys/fs/cgroup/l3mdev/cgroup.procs
	else
		vrf_cgroup_exists $vrf || return 1
		echo $pid >> /sys/fs/cgroup/l3mdev/${vrf}/cgroup.procs
	fi
}

function task_exec
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	shift

	vrf_exists $vrf
	if [ $? -ne 0 ]; then
		echo "ERROR: VRF does not exist"
		return 1
	fi

	if [ -z "$1" ]; then
		echo "ERROR: No command to run"
		return 1
	fi

	if [ "$vrf" = "default" ]; then
		cgexec -g l3mdev:/ $*
	else
		vrf_cgroup_exists $vrf >/dev/null 2>&1
		if [ $? -ne 0 ]; then
			echo "ERROR: cgroup does not exist for VRF"
			return 1
		fi

		# make sure cgroup has master device set
		cgget -g l3mdev:/${vrf} -v -n | grep -q " ==> $vrf"
		if [ $? -ne 0 ]; then
			echo "ERROR: cgroup is misconfigured for VRF"
			return 1
		fi

		cgexec -g l3mdev:/${vrf} $*
	fi
}

function task_cmd
{
	local cmd=$1
	shift

	l3mdev_cgroup_exists
	[ $? -ne 0 ] && return 1

	case "$cmd" in
		sh|show|li|list|ls) task_list $*;;
		id|identify) task_identify $*;;

		# used by vrf wrapper; needs to be hidden as there are
		# caveats to change the vrf association of a running task
		set)         task_set $*;;

		ex|exec)     task_exec $*;;

		help) task_usage;;
		*) task_usage; return 1;;
	esac
}

################################################################################
# verify config for existing vrfs

function verify_vrf_cgroup
{
	local vrf=$1

	vrf_cgroup_exists $vrf >/dev/null 2>&1
	if [ $? -ne 0 ]; then
		echo "    ERROR: cgroup does not exist"
		return 1
	fi

	idx=$(dev_index $vrf)
	if [ $? -ne 0 ]; then
		echo "    ERROR: Failed to get device index; cgroup may be misconfigured"
		return 1
	fi

	# l3mdev.master-device file has line:
	#     net[4026531957]: device index 62 ==> red
	# important part is the index N ==> dev-name

	cgget -g l3mdev:/${vrf} -v -n | grep -q "index $idx ==> $vrf"
	if [ $? -ne 0 ]; then
		echo "    ERROR: cgroup is misconfigured"
		return 1
	fi

	echo "    cgroup looks ok"

	return 0
}

function verify_local_table
{
	local rc=0

	echo
	echo "Checking rules for local table:"

	ip ru ls | egrep -q "^0:.*from all lookup local"
	if [ $? -eq 0 ]; then
		echo "    ERROR: IPv4 rule for local table should be after VRFs"
		rc=1
	fi

	ip -6 ru ls | egrep -q "^0:.*from all lookup local"
	if [ $? -eq 0 ]; then
		echo "    ERROR: IPv6 rule for local table should be after VRFs"
		rc=1
	fi

	[ $rc -eq 0 ] && echo "    rules for local tables look ok"

	return $rc
}

function ip_ru_verify
{
	local vrf=$1
	local tbid=$2
	local tmpfile=$3
	local ver=$4
	local desc="IPv4"
	local rc=0
	local d

	if [ "$ver" = "6" ]; then
		desc="IPv6"
	fi

	# make sure the FIB rules exist; routing requires it

	for d in iif oif
	do
		grep -q "from all $d $vrf lookup $vrf" ${tmpfile}
		if [ $? -ne 0 ]; then
			grep -q "from all $d $vrf lookup $tbid" ${tmpfile}
			if [ $? -ne 0 ]; then
				echo "    ERROR: $desc $d fib rule is missing"
				rc=1
			fi
		fi

		# make sure there is only 1 FIB rule; duplicate rules affect
		# performance
		n=$(grep "from all $d $vrf lookup" ${tmpfile} | wc -l)
		if [ $n -gt 1 ]; then
			echo "    WARNING: More than 1 $desc $d fib rules are present"
			rc=1
		fi

		sed -i -e "/from all $d $vrf lookup/d" ${tmpfile}
	done

	return $rc
}

function verify_vrf_fib_rules
{
	local vrf=$1
	local tbid=$2
	local rc=0

	ip_ru_verify $vrf $tbid ${IPRUFILE} "4"
	[ $? -ne 0 ] && rc=1
	ip_ru_verify $vrf $tbid ${IP6RUFILE} "6"
	[ $? -ne 0 ] && rc=1

	[ $rc -eq 0 ] && echo "    FIB rules look ok"

	return $rc
}

function verify_vrf_one
{
	local vrf=$1
	local tbid=$2
	local table_check=$3
	local rc=0
	local rm_files=no

	if [ "$table_check" = "yes" ]; then
		local tbid_chk=$(vrf_get_table $vrf)
		if [ "$tbid" != "$tbid_chk" ]; then
			echo "    Table id mismatch"
			rc=1
		fi
	fi

	[ -z "$tbid" ] && tbid=$(vrf_get_table $vrf)
	if [ -z "$tbid" ]; then
		vrf_exists ${vrf}
		if [ $? -ne 0 ]; then
			echo "    VRF does not exist"
		else
			echo "    VRF exists, but failed to get table id"
		fi
		return 1
	fi

	if [ -z ${IPRUFILE} ]; then
		IPRUFILE=$(mktemp /tmp/vrf-ip.XXXXXXXX)
		IP6RUFILE=$(mktemp /tmp/vrf-ip6.XXXXXXXX)
		ip ru ls >> ${IPRUFILE}
		ip -6 ru ls >> ${IP6RUFILE}
		rm_files=yes
	fi

	vrf_verify_default_route $tbid
	[ $? -ne 0 ] && rc=1

	verify_vrf_fib_rules $vrf $tbid
	[ $? -ne 0 ] && rc=1

	verify_vrf_cgroup $vrf
	[ $? -ne 0 ] && rc=1

	[ "$rm_files" = "yes" ] && rm -f ${IPRUFILE} ${IP6RUFILE}

	return $rc
}

function verify_vrf_all
{
	local tbid
	local rc=0
	local n=0

	n=$(ip -br link show type vrf | wc -l)
	if [ $n -eq 0 ]; then
		echo "No VRFs have been configured"
		return 0
	fi

	local vrf_list=$(vrf_get_list)

	for vrf in ${vrf_list}
	do
		tbid=$(vrf_get_table $vrf)
		if [ -z "$tbid" ]; then
			echo "ERROR: VRF $vrf: failed to get table id; VRF is misconfigured"
			rc=1
			continue
		fi

		echo
		echo "VRF $vrf table $tbid"

		verify_vrf_one ${vrf} ${tbid} "no"
	done

	verify_local_table
	[ $? -ne 0 ] && rc=1

	#
	# look for stale rules without a VRF
	#
	grep -q '\[detached\]' ${IPRUFILE}
	if [ $? -eq 0 ]; then
		echo
		echo "ERROR: Detached IPv4 FIB rules need to be removed."
	fi
	sed -i -e "/\[detached\]/d" ${IPRUFILE}

	grep -q '\[detached\]' ${IP6RUFILE}
	if [ $? -eq 0 ]; then
		echo
		echo "ERROR: Detached IPv6 FIB rules need to be removed."
	fi
	sed -i -e "/\[detached\]/d" ${IP6RUFILE}

	echo

	return $rc
}

function verify_cmd
{
	local vrf=$(get_vrf_arg ${1})
	[ $? -ne 0 ] && return 1

	local tbid=$(get_tbid_arg ${2})
	[ $? -ne 0 ] && return 1

	IPRUFILE=$(mktemp /tmp/vrf-ip.XXXXXXXX)
	IP6RUFILE=$(mktemp /tmp/vrf-ip6.XXXXXXXX)

	ip ru ls >> ${IPRUFILE}
	ip -6 ru ls >> ${IP6RUFILE}
	if [ -n "$vrf" ]; then
		local check_table="yes"

		if [ -z "$tbid" ]; then
			check_table="no"
			echo "VRF $vrf:"
		else
			echo "VRF $vrf table $tbid:"
		fi
		verify_vrf_one "$vrf" "$tbid" "$check_table"
		rc=$?
	else
		verify_vrf_all
		rc=$?
	fi

	rm -f ${IPRUFILE} ${IP6RUFILE}

	return $rc
}

################################################################################
# usage

function usage
{
	cat <<EOF
$PROG <OPTS>

VRF domains:
    $PROG list
EOF
	link_usage
	route_usage
	task_usage
}

################################################################################
# main

# enable verbose logging?
if [ "$1" = "-v" ]; then
	VERBOSE=1
	shift
fi

CMD=$1
shift
case "$CMD" in
	ls|list|sh|show) vrf_list $*;;

	# bash profile scripts for checking if a vrf exists
	# $PROG exists <vrf-name>
	exists) vrf=$(get_vrf_arg ${1}) || exit 1
		vrf_exists ${vrf}
		;;

	# task commands
	t|ta|task) task_cmd $*;;

	# allowed shortcut: $PROG exec <vrf> <cmd>
	exec) task_cmd  exec $*;;

	# vrf identify   (show VRF for task)
	id|identify)   task_identify $*;;

	li|link)  link_cmd $*;;
	ro|route) route_cmd $*;;

	# used by mgmt-vrf
	table) vrf_table $*;;

	####################################################
	# used by ifupdown2
	configure) vrf_configure $*;;
	teardown)  vrf_teardown  $*;;

	####################################################
	# these options are for testing and tooling; they
	# are not intended for use by customers
	add)        vrf_add $*;;
	del|delete) vrf_delete $*;;

	verify)     verify_cmd $*;;
	# end unsupported options
	####################################################

	help) usage; exit 0;;
	*) usage; exit 1;;
esac
