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