#!/bin/bash
#
# VRF admin script
#
# This script is mostly a wrapper around ip commands used to create and
# configure VRFs. The intent is to provide a better and cleaner user
# experience for managing VRFs on Linux.

PROG=${0##*/}

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=4278198272

# Allowed table id range
TBID_MIN=1001
TBID_MAX=1255

MISSING_IPV6_DEF_RT=6

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

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

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

# 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 name" >&2
		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 vrf_cgroup_exists
{
	local vrf=$1

	[ -e ${CGRPDIR}/${vrf} ] && return 0

	echo "cgroup does not exist for VRF."

	return 1
}

function vrf_delete_cgroup
{
	local vrf=${1}

	[ ! -e ${CGRPDIR}/${vrf} ] && return 0

	# handle transient EBUSY error by trying again
	for n in $(seq 1 5); do
		rmdir ${CGRPDIR}/${vrf} 2>/dev/null
		[ $? -eq 0 ] && return 0
		sleep 1
	done

	return 1
}

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

function systemd_start_vrf
{
	local vrf=${1}

	# start services that have been enabled in a VRF. skip services in
	# failed state (can lead to cl-support trying to restart them)
	systemctl -t service --state=active,inactive list-units *@${vrf}.service |\
	    awk '$1 ~ /.service$/ && $2 == "loaded" {print $1}' | \
	while read s
	do
		systemctl --no-block start ${s}
	done
}

# Do not call during boot
function systemd_stop_vrf
{
	local vrf=${1}

	# stop active services running in the vrf
	systemctl -t service --state=active list-units *@${vrf}.service |\
	    awk '$1 ~ /.service$/ && $2 == "loaded" {print $1}' | \
	while read s
	do
		systemctl stop ${s}
	done
}

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

function vrf_exists
{
	local vrf=${1}
	local n

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

	# ip link show dev <name> type vrf happily returns 0 even though
	# <name> is not of type vrf. Hence the wc -l 
	n=$(ip -br link show dev ${vrf} type vrf 2>/dev/null | wc -l)
	[ ${n} -eq 1 ] && return 0

	return $?
}

# get table id for device enslaved to a VRF
function vrf_get_table_dev
{
	local tbid

	# if vrf_slave is not in the output device is not enslaved
	tbid=$(ip -o -d link show dev ${1} 2>/dev/null |\
	      egrep ' vrf_slave table [0-9]*' |\
	      sed -e 's/.*vrf_slave table \([0-9]*\) .*/\1/')

	[ -z "${tbid}" ] && return 1

	echo ${tbid}

	return 0
}

# get table id for vrf device
function vrf_get_table
{
	local tbid

	tbid=$(ip -o -d link show dev ${1} 2>/dev/null |\
	      egrep ' vrf table [0-9]*' |\
	      sed -e 's/.*vrf table \([0-9]*\) .*/\1/')

	[ -z "${tbid}" ] && return 1

	echo ${tbid}

	return 0
}

function vrf_table
{
	local vrf
	local tbid

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

	vrf_exists $vrf
	if [ $? -eq 0 ]; then
		vrf_get_table $vrf
		return 0
	fi

	# maybe this is a device, not a VRF
	tbid=$(vrf_get_table_dev ${vrf})
	if [ $? -eq 0 ]; then
		echo ${tbid}
		return 0
	fi

	return 1
}

# 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

	[ ${HAVE_L3MDEV_RULE} = "yes" ] && return 0

	# 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

	[ ${HAVE_L3MDEV_RULE} = "yes" ] && return 0

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

	return 0
}

function check_l3mdev_rule
{
	# Does this kernel have the l3mdev rule?
	ip ru ls 2>/dev/null | egrep -q 'from all lookup \[l3mdev-table\]'
	if [ $? -eq 0 ]; then
		HAVE_L3MDEV_RULE=yes
	else
		HAVE_L3MDEV_RULE=no
	fi
}

# 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_v4
{
	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
}

function vrf_add_default_route_v6
{
	local tbid=$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_add_default_route
{
	local tbid=$1

	vrf_add_default_route_v4 ${tbid}
	vrf_add_default_route_v6 ${tbid}
}

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 -eq 0 ] && rc=${MISSING_IPV6_DEF_RT}
	fi

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

	return $rc
}

# Configure a VRF
function vrf_configure
{
	local vrf
	local tbid
	local rc

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

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

	# 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

	# use ip route to create the cgroup and add bpf program
	# this can always be done to reload the bpf program to
	# ensure the right index is used
	ip vrf exec ${vrf} id >/dev/null 2>&1

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

	# special case rebuilds - ifdown can take down the 'lo'
	# device which removes the default route in the IPv6 table
	# rather than rebuild the VRF for an easy fix, just replace
	# the default route. For any other errors, the VRF is
	# rebuilt
	if [ ${rc} -eq ${MISSING_IPV6_DEF_RT} ]; then
		vrf_add_default_route_v6 ${tbid}
		[ $? -eq 0 ] && return 0
	fi

	# make sure old remnants of prior vrf are removed
	# no need if this boot up
	if [ -z "${mode}" ]; then
		vrf_teardown ${vrf} ${tbid} 2>/dev/null
	fi

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

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

	return 0
}

# Remove all configuration for VRF
function vrf_teardown
{
	local vrf
	local tbid
	local mode=$3

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

	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}
	if [ $? -ne 0 ]; then
		echo "Failed to delete cgroup for vrf ${vrf}"
		return 1
	fi

	return 0
}

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
	local tbid

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

	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}

	# l3mdev rule is added on first dev create
	check_l3mdev_rule

	# 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
	local tbid

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

	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
}

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_cmd
{
	local cmd=$1
	shift

	case "$cmd" in
		ls|list|sh|show) link_list   $*;;
		help)         link_usage;;
		*)            link_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

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

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

	ip vrf pids ${vrf}
}

# 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=$(ip vrf id $pid)
	if [ $? -ne 0 ]; then
		return 1
	fi

	if [ -n "$c" ]; then
		[ $do_prompt = "1" ] && c=":$c"
	elif [ $do_prompt != "1" ]; then
		c="default"
	fi
	echo "$c"
}

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

	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 [ -z "${CGRPDIR}" ]; then
		if [ "$vrf" != "default" ]; then
			echo "cgroup2 not mounted; can not change vrf"
		fi
		return 1
	fi

	# cgroup has not been configured; use ip-vrf-exec to create
	# the cgroup with bpf filter so we can set the task id
	ip vrf exec ${vrf} id >/dev/null 2>&1

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

function task_exec
{
	local vrf

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

	shift

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

	exec ip vrf exec ${vrf} $*
}

function task_cmd
{
	local cmd=$1
	shift

	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_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

	[ ${HAVE_L3MDEV_RULE} = "yes" ] && return 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
	local tmp_rc

	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}" -a "${HAVE_L3MDEV_RULE}" != "yes" ]; 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

	# keep rc and pass back to caller; used to catch missing
	# ipv6 default route
	vrf_verify_default_route $tbid
	tmp_rc=$?
	[ ${tmp_rc} -ne 0 ] && rc=${tmp_rc}

	verify_vrf_fib_rules $vrf $tbid
	[ $? -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

	if [ ${HAVE_L3MDEV_RULE} = "yes" ]; then
		echo
		echo "Checking l3mdev-table rules"
		ip -6 ru ls | egrep -q 'from all lookup \[l3mdev-table\]'
		if [ $? -ne 0 ]; then
			echo "    ERROR: IPv6 FIB rule for l3mdev-table is missing."
			rc=1
		else
			echo "    FIB rules are ok."
		fi
	fi

	#
	# 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
	local tbid

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

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

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

	if [ "${HAVE_L3MDEV_RULE}" != "yes" ]; then
		ip ru ls >> ${IPRUFILE}
		ip -6 ru ls >> ${IP6RUFILE}
	fi

	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
	task_usage
}

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

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

CGRPDIR=$(mount |awk '$0 ~ /type cgroup2/{print $3}')
if [ -n "${CGRPDIR}" ]; then
	vpath=$(grep '::/' /proc/$$/cgroup)
	vpath=${vpath//*::/}
	vpath=${vpath//\/vrf*/}
	CGRPDIR=${CGRPDIR}${vpath}/vrf
fi

check_l3mdev_rule

CMD=$1
shift
case "$CMD" in
	ls|list|sh|show) ip 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 $*;;

	# used by mgmt-vrf and dhclient exit hook
	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
