1#!/bin/sh 2# 3# SPDX-License-Identifier: BSD-2-Clause 4# 5# Copyright (c) 2016-2026 Devin Teske <dteske@FreeBSD.org> 6# 7############################################################ IDENT(1) 8# 9# $Title: netgraph(4) management script for vnet jails $ 10# $Version: 9.4 $ 11# 12############################################################ INFORMATION 13# 14# Use this tool with jail.conf(5) (or rc.conf(5) ``legacy'' configuration) to 15# manage `vnet' interfaces for jails. Designed to automate the creation of vnet 16# interface(s) during jail `prestart', return them to the host during jail 17# `prestop', and destroy said interface(s) during jail `poststop'. 18# 19# In jail.conf(5) format: 20# 21# ### BEGIN EXCERPT ### 22# 23# xxx { 24# host.hostname = "xxx.yyy"; 25# path = "/vm/$name"; 26# 27# # 28# # NB: Below 2-lines required 29# # NB: The number of ngN_$name interfaces should match the number of 30# # arguments given to `jng bridge $name' in exec.prestart value. 31# # 32# vnet; 33# vnet.interface = ng0_$name, ng1_$name, ...; 34# 35# exec.clean; 36# exec.system_user = "root"; 37# exec.jail_user = "root"; 38# 39# # 40# # NB: Below lines required 41# # NB: The number of arguments after `jng bridge $name' should match 42# # the number of ngN_$name arguments in vnet.interface value. 43# # NB: Return each ngN_$name to the host in prestop so the kernel does 44# # not move it during jail removal (BPF race). Destroy in poststop. 45# # 46# exec.prestart += "jng bridge $name em0 em1 ..."; 47# exec.prestop += "ifconfig ng0_$name -vnet $name"; 48# exec.prestop += "ifconfig ng1_$name -vnet $name"; 49# exec.poststop += "jng shutdown $name"; 50# 51# # Standard recipe 52# exec.start += "/bin/sh /etc/rc"; 53# exec.stop = "/bin/sh /etc/rc.shutdown jail"; 54# exec.consolelog = "/var/log/jail_${name}_console.log"; 55# mount.devfs; 56# 57# # Optional (default off) 58# #allow.mount; 59# #allow.set_hostname = 1; 60# #allow.sysvipc = 1; 61# #devfs_ruleset = "11"; # rule to unhide bpf for DHCP 62# } 63# 64# ### END EXCERPT ### 65# 66# In rc.conf(5) ``legacy'' format (used when /etc/jail.conf does not exist): 67# 68# ### BEGIN EXCERPT ### 69# 70# jail_enable="YES" 71# #jail_confwarn="NO" # Optional: disable warning to migrate to jail.conf(5) 72# jail_list="xxx" 73# 74# # 75# # Global presets for all jails 76# # 77# jail_devfs_enable="YES" # mount devfs 78# 79# # 80# # Global options (default off) 81# # 82# #jail_mount_enable="YES" # mount /etc/fstab.{name} 83# #jail_set_hostname_allow="YES" # Allow hostname to change 84# #jail_sysvipc_allow="YES" # Allow SysV Interprocess Comm. 85# 86# # xxx 87# jail_xxx_hostname="xxx.shxd.cx" # hostname 88# jail_xxx_rootdir="/vm/xxx" # root directory 89# jail_xxx_vnet_interfaces="ng0_xxx ng1xxx ..." # vnet interface(s) 90# jail_xxx_exec_prestart0="jng bridge xxx em0 em1 ..." # bridge interface(s) 91# jail_xxx_exec_prestop0="ifconfig ng0_xxx -vnet xxx" # return ifnet(s) 92# jail_xxx_exec_prestop1="ifconfig ng1_xxx -vnet xxx" 93# jail_xxx_exec_poststop0="jng shutdown xxx" # destroy interface(s) 94# #jail_xxx_mount_enable="YES" # mount /etc/fstab.xxx 95# #jail_xxx_devfs_ruleset="11" # rule to unhide bpf for DHCP 96# 97# ### END EXCERPT ### 98# 99# Note that the legacy rc.conf(5) format is converted to 100# /var/run/jail.{name}.conf by /etc/rc.d/jail if jail.conf(5) is missing. 101# 102# ASIDE: dhclient(8) inside a vnet jail... 103# 104# To allow dhclient(8) to work inside a vnet jail, make sure the following 105# appears in /etc/devfs.rules (which should be created if it doesn't exist): 106# 107# [devfsrules_jail=11] 108# add include $devfsrules_hide_all 109# add include $devfsrules_unhide_basic 110# add include $devfsrules_unhide_login 111# add path 'bpf*' unhide 112# 113# And set ether devfs.ruleset="11" (jail.conf(5)) or 114# jail_{name}_devfs_ruleset="11" (rc.conf(5)). 115# 116# NB: While this tool can't create every type of desirable topology, it should 117# handle most setups, minus some considered exotic or purpose-built. 118# 119# Uplink on ng_ether(4) `lower' (jng 7+) keeps the WAN MAC table small: 120# ng_bridge(4) does not learn on uplink hooks. The first connected hook being 121# uplink also selects restrictive unknown-unicast: frames for an unknown dest 122# go only to uplink, not to jail links. Inbound unicast to a jail therefore 123# requires that jail's MAC to live in the forwarding database (FDB) on the 124# jail's link. jng 8 pins each eiface MAC with ngctl movehost and sets 125# maxStaleness so host->staleness cannot catch it. `jng pin NAME' replants 126# after an accidental move. ng_bridge must not MOVE_HOST from learnMac=0 hooks 127# or promiscuous TX echo can steal a pinned MAC onto uplink; without that 128# kernel fix, re-run `jng pin'. 129# 130############################################################ CONFIGURATION 131 132# 133# host->staleness is uint16_t; conf.maxStaleness is uint32_t. 134# ng_bridge_timeout expires when ++staleness >= maxStaleness. 135# A threshold above 65535 is unreachable (the counter wraps). 136# 137NG_BRIDGE_MAX_STALENESS=4294967295 138 139############################################################ GLOBALS 140 141VERSION='$Version: 9.4 $' 142 143pgm="${0##*/}" # Program basename 144 145# 146# Global exit status 147# 148SUCCESS=0 149FAILURE=1 150 151# 152# Command-line options 153# 154STATS_FMT=text # -j for JSON 155 156############################################################ FUNCTIONS 157 158quietly(){ "$@" > /dev/null 2>&1; } 159 160die() 161{ 162 local fmt="$1" 163 if [ "$fmt" ]; then 164 shift 1 # fmt 165 printf "%s: $fmt\n" "$pgm" "$@" >&2 166 fi 167 exit $FAILURE 168} 169 170usage() 171{ 172 local fmt="$1" 173 local optfmt="\t%-5s %s\n" 174 local action usage descr 175 exec >&2 176 if [ "$fmt" ]; then 177 shift 1 # fmt 178 printf "%s: $fmt\n" "$pgm" "$@" 179 fi 180 printf "Usage: %s [-hv] action [arguments]\n" "$pgm" 181 printf "Options:\n" 182 printf "$optfmt" "-h" "Print this usage statement and exit." 183 printf "$optfmt" "-v" "Print version information and exit." 184 printf "Actions:\n" 185 for action in \ 186 bridge \ 187 graph \ 188 pin \ 189 show \ 190 show1 \ 191 shutdown \ 192 stats \ 193 ; do 194 eval usage=\"\$jng_${action}_usage\" 195 [ "$usage" ] || continue 196 eval descr=\"\$jng_${action}_descr\" 197 printf "\t%s\n\t\t%s\n" "$usage" "$descr" 198 done 199 die 200} 201 202action_usage() 203{ 204 local usage descr action="$1" fmt="$2" 205 shift 1 # action 206 if [ "$fmt" ]; then 207 shift 1 # fmt 208 printf "%s: %s: $fmt\n" "$pgm" "$action" "$@" >&2 209 fi 210 eval usage=\"\$jng_${action}_usage\" 211 printf "Usage: %s %s\n" "$pgm" "$usage" >&2 212 eval descr=\"\$jng_${action}_descr\" 213 printf "\t%s\n" "$descr" >&2 214 die 215} 216 217iface_encode() 218{ 219 LC_ALL=C iface="$1" awk 'BEGIN { 220 for (n = 0; n < 256; n++) 221 pack[sprintf("%c", n)] = sprintf("_%02x", n) 222 numbers = "0123456789" 223 uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 224 lowercase = "abcdefghijklmnopqrstuvwxyz" 225 valid = "[" numbers uppercase lowercase "]" 226 iface = ENVIRON["iface"] 227 len = length(iface) 228 for (n = 1; n <= len; n++) { 229 let = substr(iface, n, 1) 230 _iface = _iface (let ~ valid ? let : pack[let]) 231 } 232 print _iface 233 }' 234} 235 236derive_mac() 237{ 238 local OPTIND=1 OPTARG __flag 239 local __mac_num= __make_pair= 240 while getopts 2n: __flag; do 241 case "$__flag" in 242 2) __make_pair=1 ;; 243 n) __mac_num=${OPTARG%%[^0-9]*} ;; 244 esac 245 done 246 shift $(( $OPTIND - 1 )) 247 248 local __iface="$1" 249 if [ ! "$__mac_num" ]; then 250 local __iface_encoded 251 __iface_encoded=$( iface_encode "$__iface" ) 252 eval __mac_num=\${_${__iface_encoded}_num:--1} 253 __mac_num=$(( $__mac_num + 1 )) 254 eval _${__iface_encoded}_num=\$__mac_num 255 fi 256 257 local __name="$2" __var_to_set="$3" __var_to_set_b="$4" 258 local __iface_devid __new_devid __num __new_devid_b 259 # 260 # Calculate MAC address derived from given iface. 261 # 262 # The formula used is ``NP:SS:SS:II:II:II'' where: 263 # + N denotes 4 bits used as a counter to support branching 264 # each parent interface up to 15 times under the same jail 265 # name (see S below). 266 # + P denotes the special nibble whose value, if one of 267 # 2, 6, A, or E (but usually 2) denotes a privately 268 # administered MAC address (while remaining routable). 269 # + S denotes 16 bits, the sum(1) value of the jail name. 270 # + I denotes bits that are inherited from parent interface. 271 # 272 # The S bits are a CRC-16 checksum of NAME, allowing the jail 273 # to change link numbers in ng_bridge(4) without effecting the 274 # MAC address. Meanwhile, if... 275 # + the jail NAME changes (e.g., it was duplicated and given 276 # a new name with no other changes) 277 # + the underlying network interface changes 278 # + the jail is moved to another host 279 # the MAC address will be recalculated to a new, similarly 280 # unique value preventing conflict. 281 # 282 __iface_devid=$( ifconfig $__iface ether | awk '/ether/,$0=$2' ) 283 # ??:??:??:II:II:II 284 __new_devid=${__iface_devid#??:??:??} # => :II:II:II 285 # => :SS:SS:II:II:II 286 __num=$( set -- $( echo -n "$__name" | sum ) && echo $1 ) 287 __new_devid=$( printf :%02x:%02x \ 288 $(( $__num >> 8 & 255 )) $(( $__num & 255 )) )$__new_devid 289 # => P:SS:SS:II:II:II 290 case "$__iface_devid" in 291 ?2:*) __new_devid=a$__new_devid __new_devid_b=e$__new_devid ;; 292 ?[Ee]:*) __new_devid=2$__new_devid __new_devid_b=6$__new_devid ;; 293 *) __new_devid=2$__new_devid __new_devid_b=e$__new_devid 294 esac 295 # => NP:SS:SS:II:II:II 296 __new_devid=$( printf %x $(( $__mac_num & 15 )) )$__new_devid 297 __new_devid_b=$( printf %x $(( $__mac_num & 15 )) )$__new_devid_b 298 299 # 300 # Return derivative MAC address(es) 301 # 302 if [ "$__make_pair" ]; then 303 if [ "$__var_to_set" -a "$__var_to_set_b" ]; then 304 eval $__var_to_set=\$__new_devid 305 eval $__var_to_set_b=\$__new_devid_b 306 else 307 echo $__new_devid $__new_devid_b 308 fi 309 else 310 if [ "$__var_to_set" ]; then 311 eval $__var_to_set=\$__new_devid 312 else 313 echo $__new_devid 314 fi 315 fi 316} 317 318mustberoot_to_continue() 319{ 320 [ "$( id -u )" -eq 0 ] || die "Must run as root!" 321} 322 323jng_bridge_has_uplink() 324{ 325 ngctl show "$1:" 2> /dev/null | awk ' 326 $1 ~ /^uplink/ { found = 1; exit } 327 END { exit !found } 328 ' # END-QUOTE 329} 330 331jng_bridge_persist_hosts() 332{ 333 local node="$1" 334 local debug=0 loop=60 stable=1 config 335 336 eval $( ngctl msg "$node:" getconfig 2> /dev/null | awk ' 337 { 338 if (match($0, /debugLevel=[0-9]+/)) 339 printf "debug=%s ", 340 substr($0, RSTART + 11, RLENGTH - 11) 341 if (match($0, /loopTimeout=[0-9]+/)) 342 printf "loop=%s ", 343 substr($0, RSTART + 12, RLENGTH - 12) 344 if (match($0, /minStableAge=[0-9]+/)) 345 printf "stable=%s ", 346 substr($0, RSTART + 13, RLENGTH - 13) 347 } 348 ' ) 349 config="debugLevel=$debug" 350 config="$config loopTimeout=$loop" 351 config="$config maxStaleness=$NG_BRIDGE_MAX_STALENESS" 352 config="$config minStableAge=$stable" 353 quietly ngctl msg "$node:" setconfig "{ $config }" 354} 355 356jng_pin_mac() 357{ 358 local node="$1" mac="$2" hook="$3" 359 360 [ "$node" -a "$mac" -a "$hook" ] || return $FAILURE 361 quietly ngctl msg "$node:" movehost "{ addr=$mac hook=\"$hook\" }" 362} 363 364jng_jiface_mac() 365{ 366 local __jiface="$1" __jail="$2" __var_to_set="$3" 367 local __mac 368 369 __mac=$( ifconfig "$__jiface" ether 2> /dev/null | 370 awk '/ether/ { print $2; exit }' ) 371 if [ ! "$__mac" -a "$__jail" ]; then 372 # 373 # After vnet.interface takes the eiface, it is gone 374 # from the host ifconfig; netgraph node remains. 375 # 376 __mac=$( jexec "$__jail" ifconfig "$__jiface" ether \ 377 2> /dev/null | awk '/ether/ { print $2; exit }' ) 378 fi 379 eval $__var_to_set=\"\$__mac\" 380 [ "$__mac" ] 381} 382 383jng_pin_jiface() 384{ 385 local jiface="$1" jail="$2" 386 local mac peer peerhook pbridge phook 387 388 jng_jiface_mac "$jiface" "$jail" mac || return $FAILURE 389 390 # ether <peer> bridge <id> <peerhook> 391 set -- $( ngctl show "$jiface:" 2> /dev/null | awk ' 392 $3 == "bridge" { print $2, $5; exit } 393 ' ) 394 peer="$1" peerhook="$2" 395 [ "$peer" -a "$peerhook" ] || return $FAILURE 396 397 jng_pin_mac "$peer" "$mac" "$peerhook" || return 398 if jng_bridge_has_uplink "$peer"; then 399 jng_bridge_persist_hosts "$peer" || : persist optional 400 return 401 fi 402 403 # 404 # Secondary bridge: also pin on the parent that holds uplink 405 # (restrictive unknown-unicast lives there). 406 # 407 set -- $( ngctl show "$peer:" 2> /dev/null | awk ' 408 $3 == "bridge" { print $2, $5; exit } 409 ' ) 410 pbridge="$1" phook="$2" 411 [ "$pbridge" -a "$phook" ] || return $SUCCESS 412 jng_pin_mac "$pbridge" "$mac" "$phook" || return 413 jng_bridge_persist_hosts "$pbridge" || : persist optional 414} 415 416ng_ether_sanitize_ifname() 417{ 418 # NB: Emulates function of same name in sys/netgraph/ng_ether.c 419 ifname="$1" awk 'BEGIN { 420 _ifname = ENVIRON["ifname"] 421 gsub(/[.:]/, "_", _ifname) 422 print _ifname 423 }' 424} 425 426jng_bridge_usage="bridge [-h] [-b BRIDGE_NAME] NAME [!|=]iface0 [[!|=]iface1 ...]" 427jng_bridge_descr="Create ng0_NAME [ng1_NAME ...]" 428jng_bridge() 429{ 430 local OPTIND=1 OPTARG flag bridge=bridge 431 while getopts b:h flag; do 432 case "$flag" in 433 b) bridge="$OPTARG" 434 [ "$bridge" ] || 435 action_usage bridge "-b argument cannot be empty" 436 ;; # NOTREACHED 437 *) action_usage bridge # NOTREACHED 438 esac 439 done 440 shift $(( $OPTIND - 1 )) 441 442 [ $# -gt 0 ] || action_usage bridge "too few arguments" # NOTREACHED 443 444 local name="$1" 445 [ "${name:-x}" = "${name#*[![:print:]]}" ] || 446 action_usage bridge "invalid bridge name: %s" "$name" 447 # NOTREACHED 448 shift 1 # name 449 450 mustberoot_to_continue 451 452 local iface node parent jiface jiface_devid 453 local new clone_mac no_derive num quad mtu i=0 454 for iface in $*; do 455 456 clone_mac= 457 no_derive= 458 case "$iface" in 459 =*) iface=${iface#=} clone_mac=1 ;; 460 !*) iface=${iface#!} no_derive=1 ;; 461 esac 462 463 # ngctl(8) treats `.' and `:' as control characters, so 464 # ng_ether(4) names its node after the sanitized ifname 465 node=$( ng_ether_sanitize_ifname "$iface" ) 466 467 # Make sure the interface doesn't exist already 468 jiface=ng${i}_$name 469 if quietly ngctl msg "$jiface:" getifname; then 470 i=$(( $i + 1 )) 471 continue 472 fi 473 474 # Bring the interface up 475 ifconfig $iface up || return 476 477 # Set promiscuous mode and don't overwrite src addr 478 ngctl msg $node: setpromisc 1 || return 479 ngctl msg $node: setautosrc 0 || return 480 481 # Make sure the interface has been bridged 482 # NB: You must connect uplinkX before linkX 483 # NB: see ng_bridge(4) for policy on first connected hook 484 if ! quietly ngctl info ${node}bridge:; then 485 ngctl mkpeer $node: bridge lower uplink1 || return 486 ngctl connect $node: $node:lower upper link0 || 487 return 488 ngctl name $node:lower ${node}bridge || return 489 jng_bridge_persist_hosts ${node}bridge || 490 : persist optional 491 fi 492 493 mtu=$( ifconfig $iface | sed -n '1s/^.*mtu //p' ) || return 494 495 # Optionally create a secondary bridge 496 # NB: This time, you want to only connect linkX (no uplinkX) 497 if [ "$bridge" != "bridge" ] && 498 ! quietly ngctl info "$node$bridge:" 499 then 500 num=1 501 while quietly ngctl msg ${node}bridge: getstats $num 502 do 503 num=$(( $num + 1 )) 504 done 505 ngctl mkpeer $node:lower bridge link$num link0 || 506 return 507 ngctl name ${node}bridge:link$num "$node$bridge" || 508 return 509 fi 510 511 # Create a new interface to the bridge 512 num=1 513 while quietly ngctl msg "$node$bridge:" getstats $num; do 514 num=$(( $num + 1 )) 515 done 516 ngctl mkpeer "$node$bridge:" eiface link$num ether || return 517 518 # Rename the new interface 519 while [ ${#jiface} -gt 15 ]; do # OS limitation 520 jiface=${jiface%?} 521 done 522 new=$( ngctl show -n "$node$bridge:link$num" ) || return 523 new=$( set -- $new; echo $2 ) 524 ngctl name "$node$bridge:link$num" $jiface || return 525 ifconfig $new name $jiface || return 526 ifconfig $jiface mtu $mtu || return 527 ifconfig $jiface up || return 528 529 # 530 # Set the MAC address of the new interface using a sensible 531 # algorithm to prevent conflicts on the network. 532 # 533 jiface_devid= 534 if [ "$clone_mac" ]; then 535 jiface_devid=$( ifconfig $iface ether | 536 awk '/ether/,$0=$2' ) 537 elif [ ! "$no_derive" ]; then 538 derive_mac $iface "$name" jiface_devid 539 fi 540 [ "$jiface_devid" ] && 541 quietly ifconfig $jiface ether $jiface_devid 542 jng_pin_jiface "$jiface" "$name" || : pin optional 543 544 i=$(( $i + 1 )) 545 done # for iface 546} 547 548jng_pin_usage="pin [-h] {-a | NAME ...}" 549jng_pin_descr="Pin eiface MACs into ng_bridge forwarding database (FDB)" 550jng_pin() 551{ 552 local OPTIND=1 OPTARG flag 553 local show_all= err=$SUCCESS 554 local name iface node jiface 555 556 while getopts ah flag; do 557 case "$flag" in 558 a) show_all=1 ;; 559 *) action_usage pin # NOTREACHED 560 esac 561 done 562 shift $(( $OPTIND - 1 )) 563 if [ "$show_all" ]; then 564 [ $# -eq 0 ] || 565 action_usage pin "too many arguments" # NOTREACHED 566 for iface in $( ifconfig -l ); do 567 node=$( ng_ether_sanitize_ifname "$iface" ) 568 quietly ngctl info ${node}bridge: || continue 569 jng_bridge_persist_hosts ${node}bridge || 570 : persist optional 571 done 572 set -- $( jls -q name 2> /dev/null ) 573 [ $# -gt 0 ] || 574 action_usage pin "no jails" # NOTREACHED 575 else 576 [ $# -gt 0 ] || 577 action_usage pin "too few arguments" # NOTREACHED 578 fi 579 580 mustberoot_to_continue 581 582 for name in "$@"; do 583 [ "${name:-x}" = "${name#*[![:print:]]}" ] || 584 action_usage pin "invalid name: %s" "$name" 585 # NOTREACHED 586 for jiface in $( jexec "$name" ifconfig -l 2> /dev/null ) 587 do 588 case "$jiface" in 589 ng[0-9]*) 590 jng_pin_jiface "$jiface" "$name" || { 591 echo "$pgm: pin $jiface: failed" >&2 592 err=$FAILURE 593 } 594 ;; 595 esac 596 done 597 done 598 return $err 599} 600 601jng_graph_usage="graph [-fh] [-T type] [-o output]" 602jng_graph_descr="Generate network graph (default output is 'jng.svg')" 603jng_graph() 604{ 605 local OPTIND=1 OPTARG flag 606 local output=jng.svg output_type= force= 607 while getopts fho:T: flag; do 608 case "$flag" in 609 f) force=1 ;; 610 o) output="$OPTARG" ;; 611 T) output_type="$OPTARG" ;; 612 *) action_usage graph # NOTREACHED 613 esac 614 done 615 shift $(( $OPTIND - 1 )) 616 617 [ $# -eq 0 ] || action_usage graph "too many arguments" # NOTREACHED 618 619 mustberoot_to_continue 620 621 if [ -e "$output" -a ! "$force" ]; then 622 echo "$output: Already exists (use '-f' to overwrite)" >&2 623 return $FAILURE 624 fi 625 if [ ! "$output_type" ]; then 626 local valid suffix 627 valid=$( dot -Txxx 2>&1 ) 628 for suffix in ${valid##*:}; do 629 [ "$output" != "${output%.$suffix}" ] || continue 630 output_type=$suffix 631 break 632 done 633 fi 634 ngctl dot | dot ${output_type:+-T "$output_type"} -o "$output" 635} 636 637jng_show_usage="show [-h]" 638jng_show_descr="List possible NAME values for 'show NAME'" 639jng_show1_usage="show [-h] NAME ..." 640jng_show1_descr="Lists ng0_NAME [ng1_NAME ...]" 641jng_show2_usage="show [NAME ...]" 642jng_show2_descr="List NAME values or show interfaces associated with NAME." 643jng_show() 644{ 645 local OPTIND=1 OPTARG flag 646 local name 647 while getopts h flag; do 648 case "$flag" in 649 *) action_usage show2 # NOTREACHED 650 esac 651 done 652 shift $(( $OPTIND - 1 )) 653 654 mustberoot_to_continue 655 656 if [ $# -eq 0 ]; then 657 ngctl ls | awk '$4=="bridge",$0=$2' | 658 xargs -rn1 -Ibridge ngctl show bridge: | 659 awk 'sub(/^ng[[:digit:]]+_/, "", $2), $0 = $2' | 660 sort -u 661 return 662 fi 663 for name in "$@"; do 664 ngctl ls | awk -v name="$name" ' 665 match($2, /^ng[[:digit:]]+_/) && 666 substr($2, RSTART + RLENGTH) == name && 667 $4 == "eiface", $0 = $2 668 ' | sort 669 done 670} 671 672jng_shutdown_usage="shutdown [-h] NAME ..." 673jng_shutdown_descr="Shutdown ng0_NAME [ng1_NAME ...]" 674jng_shutdown() 675{ 676 local OPTIND=1 OPTARG flag 677 while getopts h flag; do 678 case "$flag" in 679 *) action_usage shutdown # NOTREACHED 680 esac 681 done 682 shift $(( $OPTIND -1 )) 683 684 [ $# -gt 0 ] || action_usage shutdown "too few arguments" # NOTREACHED 685 686 mustberoot_to_continue 687 688 local name 689 for name in "$@"; do 690 [ "${name:-x}" = "${name#*[![:print:]]}" ] || 691 action_usage shutdown "invalid name: %s" "$name" 692 # NOTREACHED 693 jng_show "$name" | xargs -rn1 -I jiface ngctl shutdown jiface: 694 done 695} 696 697jng_stats_usage="stats [-hj] {-a | NAME ...}" 698jng_stats_descr="Show ng_bridge link statistics for NAME interfaces" 699jng_stats() 700{ 701 local OPTIND=1 OPTARG flag 702 local show_all= 703 local name iface node ether= 704 while getopts ahj flag; do 705 case "$flag" in 706 a) show_all=1 ;; 707 j) STATS_FMT=json 708 export pgm 709 : "${HOSTNAME:=$( hostname )}" 710 export HOSTNAME 711 ;; 712 *) action_usage stats # NOTREACHED 713 esac 714 done 715 shift $(( $OPTIND -1 )) 716 if [ "$show_all" ]; then 717 [ $# -eq 0 ] || 718 action_usage stats "too many arguments" # NOTREACHED 719 720 # Get a list of bridged ng_ether(4) devices 721 for iface in $( ifconfig -l ); do 722 node=$( ng_ether_sanitize_ifname "$iface" ) 723 quietly ngctl info ${node}bridge: || continue 724 ether="$ether $iface" 725 done 726 set -- $ether $( "$0" show ) 727 [ $# -gt 0 ] || 728 action_usage stats "no bridged interfaces" # NOTREACHED 729 else 730 [ $# -gt 0 ] || 731 action_usage stats "too few arguments" # NOTREACHED 732 fi 733 734 mustberoot_to_continue 735 736 local now="$( date +%s )" 737 for name in "$@"; do 738 [ "${name:-x}" = "${name#*[![:print:]]}" ] || 739 action_usage stats "invalid name: %s" "$name" 740 # NOTREACHED 741 if ifconfig -l | xargs -n1 2> /dev/null | fgrep -qw "$name" 742 then 743 node=$( ng_ether_sanitize_ifname "$name" ) 744 [ "$STATS_FMT" != "text" ] || 745 echo "${node}bridge:uplink1 [lower]" 746 ngctl msg ${node}bridge: getstats -1 | 747 fmt_stats -n "${name}.lower" -t "$now" 748 749 [ "$STATS_FMT" != "text" ] || 750 echo "${node}bridge:link0 [upper]" 751 ngctl msg ${node}bridge: getstats 0 | 752 fmt_stats -n "${name}.upper" -t "$now" 753 fi 754 local jiface 755 for jiface in $( jng_show "$name" ); do 756 [ "$STATS_FMT" != "text" ] || echo "$jiface:" 757 ngctl show $jiface: | awk ' 758 $3 == "bridge" && $5 ~ /^link/ { 759 bridge = $2 760 link = substr($5, 5) 761 system(sprintf("ngctl msg %s: getstats %u", 762 bridge, link)) 763 }' | fmt_stats -n "$jiface" -t "$now" 764 done 765 done 766} 767fmt_stats() 768{ 769 local OPTIND=1 OPTARG flag 770 local time= 771 while getopts n:t: flag; do 772 case "$flag" in 773 n) name="$OPTARG" ;; 774 t) time="$OPTARG" ;; 775 *) break 776 esac 777 done 778 shift $(( OPTIND - 1 )) 779 fmt 2 | awk -v fmt="$STATS_FMT" -v name="$name" -v tm="$time" ' 780 function json_add_str(pre, k, s) 781 { 782 return sprintf("%s,\"%s\":\"%s\"", pre, k, s) 783 } 784 function json_add_int(pre, k, i) 785 { 786 return sprintf("%s,\"%s\":%d", pre, k, i) 787 } 788 BEGIN { 789 if (fmt == "json") { 790 if (tm == "") srand() # Time-seed 791 js = json_add_int(js, "epoch", 792 tm != "" ? tm : srand()) 793 js = json_add_str(js, "hostname", 794 ENVIRON["HOSTNAME"]) 795 js = json_add_str(js, "program", 796 ENVIRON["pgm"]) 797 js = json_add_str(js, "name", name) 798 } 799 } 800 /=/ && fl = index($0, "=") { 801 key = substr($0, 0, fl-1) 802 val = substr($0, fl+1) 803 if (fmt == "json") { 804 js = json_add_int(js, key, val) 805 } else { # Multi-line text 806 printf "%20s = %s\n", key, val 807 } 808 } 809 END { 810 if (fmt == "json") { 811 print "{" substr(js, 2) "}" 812 } 813 } 814 ' # END-QUOTE 815} 816 817############################################################ MAIN 818 819# 820# Command-line arguments 821# 822[ $# -gt 0 ] || usage "too few arguments" # NOTREACHED 823action="$1" 824[ "$action" ] || usage # NOTREACHED 825 826# 827# Validate action argument 828# 829case "$action" in 830-h) usage ;; # NOTREACHED 831-v) VERSION="${VERSION#*: }" 832 echo "${VERSION% $}" 833 exit $SUCCESS ;; 834-*) usage "unknown option: %s" "$action" ;; # NOTREACHED 835*[!a-zA-Z0-9_-]*) usage 'invalid action "%s"' "$action" ;; # NOTREACHED 836esac 837if [ "$BASH_VERSION" ]; then 838 type="$( type -t "jng_$action" )" 839else 840 type="$( type "jng_$action" 2> /dev/null )" 841fi || usage 'unknown action "%s"' "$action" # NOTREACHED 842case "$type" in 843*function) 844 shift 1 # action 845 eval "jng_$action" \"\$@\" 846 ;; 847*) usage 'unknown action "%s"' "$action" # NOTREACHED 848esac 849 850################################################################################ 851# END 852################################################################################ 853