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