1295373Sdteske#!/bin/sh
2295373Sdteske#-
3295373Sdteske# Copyright (c) 2016 Devin Teske
4295373Sdteske# All rights reserved.
5295373Sdteske#
6295373Sdteske# Redistribution and use in source and binary forms, with or without
7295373Sdteske# modification, are permitted provided that the following conditions
8295373Sdteske# are met:
9295373Sdteske# 1. Redistributions of source code must retain the above copyright
10295373Sdteske#    notice, this list of conditions and the following disclaimer.
11295373Sdteske# 2. Redistributions in binary form must reproduce the above copyright
12295373Sdteske#    notice, this list of conditions and the following disclaimer in the
13295373Sdteske#    documentation and/or other materials provided with the distribution.
14295373Sdteske#
15295373Sdteske# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16295373Sdteske# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17295373Sdteske# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18295373Sdteske# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19295373Sdteske# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20295373Sdteske# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21295373Sdteske# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22295373Sdteske# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23295373Sdteske# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24295373Sdteske# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25295373Sdteske# SUCH DAMAGE.
26295373Sdteske#
27295373Sdteske# $FreeBSD$
28295373Sdteske#
29295373Sdteske############################################################ IDENT(1)
30295373Sdteske#
31295373Sdteske# $Title: netgraph(4) management script for vnet jails $
32295373Sdteske#
33295373Sdteske############################################################ INFORMATION
34295373Sdteske#
35295373Sdteske# Use this tool with jail.conf(5) (or rc.conf(5) ``legacy'' configuration) to
36295400Sdteske# manage `vnet' interfaces for jails. Designed to automate the creation of vnet
37295400Sdteske# interface(s) during jail `prestart' and destroy said interface(s) during jail
38295400Sdteske# `poststop'.
39295373Sdteske#
40295400Sdteske# In jail.conf(5) format:
41295400Sdteske#
42295373Sdteske# ### BEGIN EXCERPT ###
43295373Sdteske#
44295373Sdteske# xxx {
45295373Sdteske# 	host.hostname = "xxx.yyy";
46295373Sdteske# 	path = "/vm/xxx";
47295373Sdteske# 
48295373Sdteske# 	#
49295373Sdteske# 	# NB: Below 2-lines required
50295373Sdteske# 	# NB: The number of ngN_xxx interfaces should match the number of
51295373Sdteske# 	#     arguments given to `jng bridge xxx' in exec.prestart value.
52295373Sdteske# 	#
53295373Sdteske# 	vnet;
54295373Sdteske# 	vnet.interface = "ng0_xxx ng1_xxx ...";
55295373Sdteske# 
56295373Sdteske# 	exec.clean;
57295373Sdteske# 	exec.system_user = "root";
58295373Sdteske# 	exec.jail_user = "root";
59295373Sdteske# 
60295373Sdteske# 	#
61295373Sdteske# 	# NB: Below 2-lines required
62295373Sdteske# 	# NB: The number of arguments after `jng bridge xxx' should match
63295373Sdteske# 	#     the number of ngN_xxx arguments in vnet.interface value.
64295373Sdteske# 	#
65295373Sdteske# 	exec.prestart += "jng bridge xxx em0 em1 ...";
66295373Sdteske# 	exec.poststop += "jng shutdown xxx";
67295373Sdteske# 
68295373Sdteske# 	# Standard recipe
69295373Sdteske# 	exec.start += "/bin/sh /etc/rc";
70295373Sdteske# 	exec.stop = "/bin/sh /etc/rc.shutdown";
71295373Sdteske# 	exec.consolelog = "/var/log/jail_xxx_console.log";
72295373Sdteske# 	mount.devfs;
73295373Sdteske#
74295373Sdteske# 	# Optional (default off)
75295373Sdteske# 	#allow.mount;
76295373Sdteske# 	#allow.set_hostname = 1;
77295373Sdteske# 	#allow.sysvipc = 1;
78295373Sdteske# 	#devfs_ruleset = "11"; # rule to unhide bpf for DHCP
79295373Sdteske# }
80295373Sdteske#
81295373Sdteske# ### END EXCERPT ###
82295373Sdteske#
83295373Sdteske# In rc.conf(5) ``legacy'' format (used when /etc/jail.conf does not exist):
84295373Sdteske#
85295373Sdteske# ### BEGIN EXCERPT ###
86295373Sdteske#
87295373Sdteske# jail_enable="YES"
88295373Sdteske# jail_list="xxx"
89295373Sdteske#
90295373Sdteske# #
91295373Sdteske# # Global presets for all jails
92295373Sdteske# #
93295373Sdteske# jail_devfs_enable="YES"	# mount devfs
94295373Sdteske#
95295373Sdteske# #
96295373Sdteske# # Global options (default off)
97295373Sdteske# #
98295373Sdteske# #jail_mount_enable="YES"		# mount /etc/fstab.{name}
99295373Sdteske# #jail_set_hostname_allow="YES"	# Allow hostname to change
100295373Sdteske# #jail_sysvipc_allow="YES"		# Allow SysV Interprocess Comm.
101295373Sdteske# 
102295373Sdteske# # xxx
103295373Sdteske# jail_xxx_hostname="xxx.shxd.cx"		# hostname
104295373Sdteske# jail_xxx_rootdir="/vm/xxx"			# root directory
105295373Sdteske# jail_xxx_vnet_interfaces="ng0_xxx ng1xxx ..."	# vnet interface(s)
106295373Sdteske# jail_xxx_exec_prestart0="jng bridge xxx em0 em1 ..."	# bridge interface(s)
107295373Sdteske# jail_xxx_exec_poststop0="jng shutdown xxx"	# destroy interface(s)
108295373Sdteske# #jail_xxx_mount_enable="YES"			# mount /etc/fstab.xxx
109295373Sdteske# #jail_xxx_devfs_ruleset="11"			# rule to unhide bpf for DHCP
110295373Sdteske#
111295373Sdteske# ### END EXCERPT ###
112295373Sdteske#
113295373Sdteske# Note that the legacy rc.conf(5) format is converted to
114295373Sdteske# /var/run/jail.{name}.conf by /etc/rc.d/jail if jail.conf(5) is missing.
115295373Sdteske#
116295373Sdteske# ASIDE: dhclient(8) inside a vnet jail...
117295373Sdteske#
118295373Sdteske# To allow dhclient(8) to work inside a vnet jail, make sure the following
119295373Sdteske# appears in /etc/devfs.rules (which should be created if it doesn't exist):
120295373Sdteske#
121295373Sdteske# 	[devfsrules_jail=11]
122295373Sdteske# 	add include $devfsrules_hide_all
123295373Sdteske# 	add include $devfsrules_unhide_basic
124295373Sdteske# 	add include $devfsrules_unhide_login
125295441Sdteske# 	add path 'bpf*' unhide
126295373Sdteske#
127295373Sdteske# And set ether devfs.ruleset="11" (jail.conf(5)) or
128295373Sdteske# jail_{name}_devfs_ruleset="11" (rc.conf(5)).
129295373Sdteske#
130295373Sdteske# NB: While this tool can't create every type of desirable topology, it should
131295373Sdteske# handle most setups, minus some which considered exotic or purpose-built.
132295373Sdteske#
133295373Sdteske############################################################ GLOBALS
134295373Sdteske
135295373Sdteskepgm="${0##*/}" # Program basename
136295373Sdteske
137295373Sdteske#
138295373Sdteske# Global exit status
139295373Sdteske#
140295373SdteskeSUCCESS=0
141295373SdteskeFAILURE=1
142295373Sdteske
143295373Sdteske############################################################ FUNCTIONS
144295373Sdteske
145295373Sdteskeusage()
146295373Sdteske{
147295373Sdteske	local action usage descr
148295373Sdteske	exec >&2
149295373Sdteske	echo "Usage: $pgm action [arguments]"
150295373Sdteske	echo "Actions:"
151295373Sdteske	for action in \
152295373Sdteske		bridge		\
153295373Sdteske		graph		\
154295373Sdteske		show		\
155295373Sdteske		show1		\
156295373Sdteske		shutdown	\
157295460Sdteske		stats		\
158295373Sdteske	; do
159295373Sdteske		eval usage=\"\$jng_${action}_usage\"
160295373Sdteske		[ "$usage" ] || continue
161295373Sdteske		eval descr=\"\$jng_${action}_descr\"
162295373Sdteske		printf "\t%s\n\t\t%s\n" "$usage" "$descr"
163295373Sdteske	done
164295373Sdteske	exit $FAILURE
165295373Sdteske}
166295373Sdteske
167295373Sdteskeaction_usage()
168295373Sdteske{
169295587Sdteske	local usage descr action="$1"
170295373Sdteske	eval usage=\"\$jng_${action}_usage\"
171295373Sdteske	echo "Usage: $pgm $usage" >&2
172295587Sdteske	eval descr=\"\$jng_${action}_descr\"
173295587Sdteske	printf "\t%s\n" "$descr"
174295373Sdteske	exit $FAILURE
175295373Sdteske}
176295373Sdteske
177295546Sdteskederive_mac()
178295546Sdteske{
179295546Sdteske	local OPTIND=1 OPTARG __flag
180295546Sdteske	local __mac_num= __make_pair=
181295546Sdteske	while getopts 2n: __flag; do
182295546Sdteske		case "$__flag" in
183295546Sdteske		2) __make_pair=1 ;;
184295546Sdteske		n) __mac_num=${OPTARG%%[^0-9]*} ;;
185295546Sdteske		esac
186295546Sdteske	done
187295546Sdteske	shift $(( $OPTIND - 1 ))
188295546Sdteske
189295546Sdteske	if [ ! "$__mac_num" ]; then
190295546Sdteske		eval __mac_num=\${_${iface}_num:--1}
191295546Sdteske		__mac_num=$(( $__mac_num + 1 ))
192295546Sdteske		eval _${iface}_num=\$__mac_num
193295546Sdteske	fi
194295546Sdteske
195295546Sdteske	local __iface="$1" __name="$2" __var_to_set="$3" __var_to_set_b="$4"
196295546Sdteske	local __iface_devid __new_devid __num __new_devid_b
197295546Sdteske	#
198295546Sdteske	# Calculate MAC address derived from given iface.
199295546Sdteske	#
200295546Sdteske	# The formula I'm using is ``NP:SS:SS:II:II:II'' where:
201295546Sdteske	# + N denotes 4 bits used as a counter to support branching
202295546Sdteske	#   each parent interface up to 15 times under the same jail
203295546Sdteske	#   name (see S below).
204295546Sdteske	# + P denotes the special nibble whose value, if one of
205295546Sdteske	#   2, 6, A, or E (but usually 2) denotes a privately
206295546Sdteske	#   administered MAC address (while remaining routable).
207295546Sdteske	# + S denotes 16 bits, the sum(1) value of the jail name.
208295546Sdteske	# + I denotes bits that are inherited from parent interface.
209295546Sdteske	#
210295546Sdteske	# The S bits are a CRC-16 checksum of NAME, allowing the jail
211295546Sdteske	# to change link numbers in ng_bridge(4) without affecting the
212295546Sdteske	# MAC address. Meanwhile, if...
213295546Sdteske	#   + the jail NAME changes (e.g., it was duplicated and given
214295546Sdteske	#     a new name with no other changes)
215295546Sdteske	#   + the underlying network interface changes
216295546Sdteske	#   + the jail is moved to another host
217295546Sdteske	# the MAC address will be recalculated to a new, similarly
218295546Sdteske	# unique value preventing conflict.
219295546Sdteske	#
220295546Sdteske	__iface_devid=$( ifconfig $__iface ether | awk '/ether/,$0=$2' )
221295553Sdteske	# ??:??:??:II:II:II
222295553Sdteske	__new_devid=${__iface_devid#??:??:??} # => :II:II:II
223295553Sdteske	# => :SS:SS:II:II:II
224295546Sdteske	__num=$( set -- `echo -n "$__name" | sum` && echo $1 )
225295553Sdteske	__new_devid=$( printf :%02x:%02x \
226295553Sdteske		$(( $__num >> 8 & 255 )) $(( $__num & 255 )) )$__new_devid
227295553Sdteske	# => P:SS:SS:II:II:II
228295546Sdteske	case "$__iface_devid" in
229295546Sdteske	   ?2:*) __new_devid=a$__new_devid __new_devid_b=e$__new_devid ;;
230295546Sdteske	?[Ee]:*) __new_devid=2$__new_devid __new_devid_b=6$__new_devid ;;
231295546Sdteske	      *) __new_devid=2$__new_devid __new_devid_b=e$__new_devid
232295546Sdteske	esac
233295553Sdteske	# => NP:SS:SS:II:II:II
234295546Sdteske	__new_devid=$( printf %x $(( $__mac_num & 15 )) )$__new_devid
235295546Sdteske	__new_devid_b=$( printf %x $(( $__mac_num & 15 )) )$__new_devid_b
236295546Sdteske
237295546Sdteske	#
238295546Sdteske	# Return derivative MAC address(es)
239295546Sdteske	#
240295546Sdteske	if [ "$__make_pair" ]; then
241295546Sdteske		if [ "$__var_to_set" -a "$__var_to_set_b" ]; then
242295546Sdteske			eval $__var_to_set=\$__new_devid
243295546Sdteske			eval $__var_to_set_b=\$__new_devid_b
244295546Sdteske		else
245295546Sdteske			echo $__new_devid $__new_devid_b
246295546Sdteske		fi
247295546Sdteske	else
248295546Sdteske		if [ "$__var_to_set" ]; then
249295546Sdteske			eval $__var_to_set=\$__new_devid
250295546Sdteske		else
251295546Sdteske			echo $__new_devid
252295546Sdteske		fi
253295546Sdteske	fi
254295546Sdteske}
255295546Sdteske
256295373Sdteskemustberoot_to_continue()
257295373Sdteske{
258295373Sdteske	if [ "$( id -u )" -ne 0 ]; then
259295373Sdteske		echo "Must run as root!" >&2
260295373Sdteske		exit $FAILURE
261295373Sdteske	fi
262295373Sdteske}
263295373Sdteske
264295554Sdteskejng_bridge_usage="bridge [-b BRIDGE_NAME] NAME [!|=]iface0 [[!|=]iface1 ...]"
265295373Sdteskejng_bridge_descr="Create ng0_NAME [ng1_NAME ...]"
266295373Sdteskejng_bridge()
267295373Sdteske{
268295373Sdteske	local OPTIND=1 OPTARG flag bridge=bridge
269295373Sdteske	while getopts b: flag; do
270295373Sdteske		case "$flag" in
271295373Sdteske		b) bridge="$OPTARG"
272295373Sdteske		   [ "$bridge" ] || action_usage bridge ;; # NOTREACHED
273295373Sdteske		*) action_usage bridge # NOTREACHED
274295373Sdteske		esac
275295373Sdteske	done
276295373Sdteske	shift $(( $OPTIND - 1 ))
277295373Sdteske
278295373Sdteske	local name="$1"
279295373Sdteske	[ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -gt 1 ] ||
280295373Sdteske		action_usage bridge # NOTREACHED
281295373Sdteske	shift 1 # name
282295373Sdteske
283295373Sdteske	mustberoot_to_continue
284295373Sdteske
285295548Sdteske	local iface parent eiface eiface_devid
286295554Sdteske	local new clone_mac no_derive num quad i=0
287295373Sdteske	for iface in $*; do
288295373Sdteske
289295548Sdteske		clone_mac=
290295554Sdteske		no_derive=
291295548Sdteske		case "$iface" in
292295548Sdteske		=*) iface=${iface#=} clone_mac=1 ;;
293295554Sdteske		!*) iface=${iface#!} no_derive=1 ;;
294295548Sdteske		esac
295295548Sdteske
296295556Sdteske		# Make sure the interface doesn't exist already
297295373Sdteske		eiface=ng${i}_$name
298295556Sdteske		if ngctl msg "$eiface:" getifname > /dev/null 2>&1; then
299295556Sdteske			i=$(( $i + 1 ))
300295556Sdteske			continue
301295556Sdteske		fi
302295373Sdteske
303295556Sdteske		# Bring the interface up
304295373Sdteske		ifconfig $iface up || return
305295373Sdteske
306295556Sdteske		# Set promiscuous mode and don't overwrite src addr
307295373Sdteske		ngctl msg $iface: setpromisc 1 || return
308295373Sdteske		ngctl msg $iface: setautosrc 0 || return
309295373Sdteske
310295556Sdteske		# Make sure the interface has been bridged
311295373Sdteske		if ! ngctl info ${iface}bridge: > /dev/null 2>&1; then
312295373Sdteske			ngctl mkpeer $iface: bridge lower link0 || return
313295373Sdteske			ngctl connect $iface: $iface:lower upper link1 ||
314295373Sdteske				return
315295373Sdteske			ngctl name $iface:lower ${iface}bridge || return
316295373Sdteske		fi
317295373Sdteske
318295556Sdteske		# Optionally create a secondary bridge
319295373Sdteske		if [ "$bridge" != "bridge" ] &&
320295373Sdteske		   ! ngctl info "$iface$bridge:" > /dev/null 2>&1
321295373Sdteske		then
322295373Sdteske			num=2
323295373Sdteske			while ngctl msg ${iface}bridge: getstats $num \
324295373Sdteske				> /dev/null 2>&1
325295373Sdteske			do
326295373Sdteske				num=$(( $num + 1 ))
327295373Sdteske			done
328295373Sdteske			ngctl mkpeer $iface:lower bridge link$num link1 ||
329295373Sdteske				return
330295373Sdteske			ngctl name ${iface}bridge:link$num "$iface$bridge" ||
331295373Sdteske				return
332295373Sdteske		fi
333295373Sdteske
334295556Sdteske		# Create a new interface to the bridge
335295373Sdteske		num=2
336295373Sdteske		while ngctl msg "$iface$bridge:" getstats $num > /dev/null 2>&1
337295373Sdteske		do
338295373Sdteske			num=$(( $num + 1 ))
339295373Sdteske		done
340295373Sdteske		ngctl mkpeer "$iface$bridge:" eiface link$num ether || return
341295373Sdteske
342295556Sdteske		# Rename the new interface
343295373Sdteske		while [ ${#eiface} -gt 15 ]; do # OS limitation
344295373Sdteske			eiface=${eiface%?}
345295373Sdteske		done
346295373Sdteske		new=$( set -- `ngctl show -n "$iface$bridge:link$num"` &&
347295373Sdteske			echo $2 ) || return
348295373Sdteske		ngctl name "$iface$bridge:link$num" $eiface || return
349295373Sdteske		ifconfig $new name $eiface || return
350295443Sdteske		ifconfig $eiface up || return
351295373Sdteske
352295373Sdteske		#
353295556Sdteske		# Set the MAC address of the new interface using a sensible
354295373Sdteske		# algorithm to prevent conflicts on the network.
355295373Sdteske		#
356295554Sdteske		eiface_devid=
357295548Sdteske		if [ "$clone_mac" ]; then
358295554Sdteske			eiface_devid=$( ifconfig $iface ether |
359295554Sdteske				awk '/ether/,$0=$2' )
360295554Sdteske		elif [ ! "$no_derive" ]; then
361295548Sdteske			derive_mac $iface "$name" eiface_devid
362295548Sdteske		fi
363295554Sdteske		[ "$eiface_devid" ] &&
364295554Sdteske			ifconfig $eiface ether $eiface_devid > /dev/null 2>&1
365295373Sdteske
366295556Sdteske		i=$(( $i + 1 ))
367295373Sdteske	done # for iface
368295373Sdteske}
369295373Sdteske
370295373Sdteskejng_graph_usage="graph [-f] [-T type] [-o output]"
371295373Sdteskejng_graph_descr="Generate network graph (default output is \`jng.svg')"
372295373Sdteskejng_graph()
373295373Sdteske{
374295373Sdteske	local OPTIND=1 OPTARG flag
375295373Sdteske	local output=jng.svg output_type= force=
376295373Sdteske	while getopts fo:T: flag; do
377295373Sdteske		case "$flag" in
378295373Sdteske		f) force=1 ;;
379295373Sdteske		o) output="$OPTARG" ;;
380295373Sdteske		T) output_type="$OPTARG" ;;
381295373Sdteske		*) action_usage graph # NOTREACHED
382295373Sdteske		esac
383295373Sdteske	done
384295373Sdteske	shift $(( $OPTIND - 1 ))
385295373Sdteske	[ $# -eq 0 -a "$output" ] || action_usage graph # NOTREACHED
386295373Sdteske	mustberoot_to_continue
387295373Sdteske	if [ -e "$output" -a ! "$force" ]; then
388295373Sdteske		echo "$output: Already exists (use \`-f' to overwrite)" >&2
389295373Sdteske		return $FAILURE
390295373Sdteske	fi
391295373Sdteske	if [ ! "$output_type" ]; then
392295373Sdteske		local valid suffix
393295373Sdteske		valid=$( dot -Txxx 2>&1 )
394295373Sdteske		for suffix in ${valid##*:}; do
395295373Sdteske			[ "$output" != "${output%.$suffix}" ] || continue
396295373Sdteske			output_type=$suffix
397295373Sdteske			break
398295373Sdteske		done
399295373Sdteske	fi
400295373Sdteske	ngctl dot | dot ${output_type:+-T "$output_type"} -o "$output"
401295373Sdteske}
402295373Sdteske
403295373Sdteskejng_show_usage="show"
404295373Sdteskejng_show_descr="List possible NAME values for \`show NAME'"
405295373Sdteskejng_show1_usage="show NAME"
406295373Sdteskejng_show1_descr="Lists ng0_NAME [ng1_NAME ...]"
407295373Sdteskejng_show2_usage="show [NAME]"
408295373Sdteskejng_show()
409295373Sdteske{
410295373Sdteske	local OPTIND=1 OPTARG flag
411295373Sdteske	while getopts "" flag; do
412295373Sdteske		case "$flag" in
413295373Sdteske		*) action_usage show2 # NOTREACHED
414295373Sdteske		esac
415295373Sdteske	done
416295373Sdteske	shift $(( $OPTIND - 1 ))
417295373Sdteske	mustberoot_to_continue
418295373Sdteske	if [ $# -eq 0 ]; then
419295373Sdteske		ngctl ls | awk '$4=="bridge",$0=$2' |
420295373Sdteske			xargs -rn1 -Ibridge ngctl show bridge: |
421295373Sdteske			awk 'sub(/^ng[[:digit:]]+_/, "", $2), $0 = $2' |
422295373Sdteske			sort -u
423295373Sdteske		return
424295373Sdteske	fi
425295373Sdteske	ngctl ls | awk -v name="$1" '
426295373Sdteske		match($2, /^ng[[:digit:]]+_/) &&
427295373Sdteske			substr($2, RSTART + RLENGTH) == name &&
428295373Sdteske			$4 == "eiface", $0 = $2
429295373Sdteske	' | sort
430295373Sdteske}
431295373Sdteske
432295373Sdteskejng_shutdown_usage="shutdown NAME"
433295373Sdteskejng_shutdown_descr="Shutdown ng0_NAME [ng1_NAME ...]"
434295373Sdteskejng_shutdown()
435295373Sdteske{
436295373Sdteske	local OPTIND=1 OPTARG flag
437295373Sdteske	while getopts "" flag; do
438295373Sdteske		case "$flag" in
439295373Sdteske		*) action_usage shutdown # NOTREACHED
440295373Sdteske		esac
441295373Sdteske	done
442295373Sdteske	shift $(( $OPTIND -1 ))
443295373Sdteske	local name="$1"
444295373Sdteske	[ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] ||
445295373Sdteske		action_usage shutdown # NOTREACHED
446295373Sdteske	mustberoot_to_continue
447295373Sdteske	jng_show "$name" | xargs -rn1 -I eiface ngctl shutdown eiface:
448295373Sdteske}
449295373Sdteske
450295460Sdteskejng_stats_usage="stats NAME"
451295460Sdteskejng_stats_descr="Show ng_bridge link statistics for NAME interfaces"
452295460Sdteskejng_stats()
453295460Sdteske{
454295460Sdteske	local OPTIND=1 OPTARG flag
455295460Sdteske	while getopts "" flag; do
456295460Sdteske		case "$flag" in
457295460Sdteske		*) action_usage stats # NOTREACHED
458295460Sdteske		esac
459295460Sdteske	done
460295460Sdteske	shift $(( $OPTIND -1 ))
461295460Sdteske	local name="$1"
462295460Sdteske	[ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] ||
463295460Sdteske		action_usage stats # NOTREACHED
464295460Sdteske	mustberoot_to_continue
465295460Sdteske	for eiface in $( jng_show "$name" ); do
466295460Sdteske		echo "$eiface:"
467295460Sdteske		ngctl show $eiface: | awk '
468295460Sdteske		$3 == "bridge" && $5 ~ /^link/ {
469295460Sdteske			bridge = $2
470295460Sdteske			link = substr($5, 5)
471295460Sdteske			system(sprintf("ngctl msg %s: getstats %u",
472295460Sdteske				bridge, link))
473295460Sdteske		}' | fmt 2 | awk '
474295460Sdteske			/=/ && fl = index($0, "=") {
475295460Sdteske				printf "%20s = %s\n",
476295460Sdteske					substr($0, 0, fl-1),
477295460Sdteske					substr($0, 0, fl+1)
478295460Sdteske			}
479295460Sdteske		' # END-QUOTE
480295460Sdteske	done
481295460Sdteske}
482295460Sdteske
483295373Sdteske############################################################ MAIN
484295373Sdteske
485295373Sdteske#
486295373Sdteske# Command-line arguments
487295373Sdteske#
488295373Sdteskeaction="$1"
489295373Sdteske[ "$action" ] || usage # NOTREACHED
490295373Sdteske
491295373Sdteske#
492295373Sdteske# Validate action argument
493295373Sdteske#
494295373Sdteskeif [ "$BASH_VERSION" ]; then
495295373Sdteske	type="$( type -t "jng_$action" )" || usage # NOTREACHED
496295373Sdteskeelse
497295373Sdteske	type="$( type "jng_$action" 2> /dev/null )" || usage # NOTREACHED
498295373Sdteskefi
499295373Sdteskecase "$type" in
500295373Sdteske*function)
501295373Sdteske	shift 1 # action
502295373Sdteske	eval "jng_$action" \"\$@\"
503295373Sdteske	;;
504295373Sdteske*) usage # NOTREACHED
505295373Sdteskeesac
506295373Sdteske
507295373Sdteske################################################################################
508295373Sdteske# END
509295373Sdteske################################################################################
510