diff --git a/share/examples/jails/jng.orig b/share/examples/jails/jng --- a/share/examples/jails/jng.orig +++ b/share/examples/jails/jng @@ -1,6 +1,6 @@ #!/bin/sh #- -# Copyright (c) 2016 Devin Teske +# Copyright (c) 2016-2024 Devin Teske # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -24,10 +24,10 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# ############################################################ IDENT(1) # # $Title: netgraph(4) management script for vnet jails $ +# $Version: 2.0 $ # ############################################################ INFORMATION # @@ -129,6 +129,26 @@ # NB: While this tool can't create every type of desirable topology, it should # handle most setups, minus some which considered exotic or purpose-built. # +############################################################ CONFIGURATION + +# +# Netgraph node type. Can be `iface' or `eiface' and refers to whether +# ng_iface(4) or ng_eiface(4) is used with ng_bridge(4). The advantages of +# choosing iface over eiface is that with iface you can utilize ng_tcpmss(4) +# to limit the TCP MSS for operating in environments that clamp down on ICMP. +# +# NB: iface/tcpmss support is EXPERIMENTAL +# +NG_TYPE=eiface # Can be iface or eiface + +# +# Clamp TCP Maximum Segment Size to reasonably below standard MTU +# NB: Fixes TCP hangup issue in environments where ICMP is restricted +# NB: Be liberal about MSS (RFC 879, section 7) +# NB: Unused unless NG_TYPE=iface +# +NG_TCPMSS_CONFIG='{ inHook="bridge" outHook="'$NG_TYPE'" maxMSS=1280 }' + ############################################################ GLOBALS pgm="${0##*/}" # Program basename @@ -139,13 +159,27 @@ SUCCESS=0 FAILURE=1 +# +# Command-line options +# +STATS_FMT=text # -j for JSON + ############################################################ FUNCTIONS +quietly(){ "$@" > /dev/null 2>&1; } + usage() { + local fmt="$1" local action usage descr exec >&2 - echo "Usage: $pgm action [arguments]" + if [ "$fmt" ]; then + shift 1 # fmt + printf "%s: $fmt\n" "$pgm" "$@" + fi + echo "Usage: $pgm [-h] action [arguments]" + echo "Options:" + printf "\t-h Print usage statement and exit.\n" echo "Actions:" for action in \ bridge \ @@ -165,7 +199,12 @@ action_usage() { - local usage descr action="$1" + local usage descr action="$1" fmt="$2" + shift 1 # action + if [ "$fmt" ]; then + shift 1 # fmt + printf "%s: %s: $fmt\n" "$pgm" "$action" "$@" >&2 + fi eval usage=\"\$jng_${action}_usage\" echo "Usage: $pgm $usage" >&2 eval descr=\"\$jng_${action}_descr\" @@ -260,28 +299,33 @@ fi } -jng_bridge_usage="bridge [-b BRIDGE_NAME] NAME [!|=]iface0 [[!|=]iface1 ...]" +jng_bridge_usage="bridge [-h] [-b BRIDGE_NAME] NAME [!|=]iface0 [[!|=]iface1 ...]" jng_bridge_descr="Create ng0_NAME [ng1_NAME ...]" jng_bridge() { local OPTIND=1 OPTARG flag bridge=bridge - while getopts b: flag; do + while getopts b:h flag; do case "$flag" in b) bridge="$OPTARG" - [ "$bridge" ] || action_usage bridge ;; # NOTREACHED + [ "$bridge" ] || + action_usage bridge "-b argument cannot be empty" + ;; # NOTREACHED *) action_usage bridge # NOTREACHED esac done shift $(( $OPTIND - 1 )) + [ $# -gt 0 ] || action_usage bridge "too few arguments" # NOTREACHED + local name="$1" - [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -gt 1 ] || - action_usage bridge # NOTREACHED + [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" ] || + action_usage bridge "invalid bridge name: %s" "$name" + # NOTREACHED shift 1 # name mustberoot_to_continue - local iface parent eiface eiface_devid + local iface parent jiface jiface_devid local new clone_mac no_derive num quad i=0 for iface in $*; do @@ -293,8 +337,8 @@ esac # Make sure the interface doesn't exist already - eiface=ng${i}_$name - if ngctl msg "$eiface:" getifname > /dev/null 2>&1; then + jiface=ng${i}_$name + if quietly ngctl msg "$jiface:" getifname; then i=$(( $i + 1 )) continue fi @@ -307,7 +351,7 @@ ngctl msg $iface: setautosrc 0 || return # Make sure the interface has been bridged - if ! ngctl info ${iface}bridge: > /dev/null 2>&1; then + if ! quietly ngctl info ${iface}bridge:; then ngctl mkpeer $iface: bridge lower link0 || return ngctl connect $iface: $iface:lower upper link1 || return @@ -316,11 +360,10 @@ # Optionally create a secondary bridge if [ "$bridge" != "bridge" ] && - ! ngctl info "$iface$bridge:" > /dev/null 2>&1 + ! quietly ngctl info "$iface$bridge:" then num=2 - while ngctl msg ${iface}bridge: getstats $num \ - > /dev/null 2>&1 + while quietly ngctl msg ${iface}bridge: getstats $num do num=$(( $num + 1 )) done @@ -332,47 +375,79 @@ # Create a new interface to the bridge num=2 - while ngctl msg "$iface$bridge:" getstats $num > /dev/null 2>&1 - do + while quietly ngctl msg "$iface$bridge:" getstats $num; do num=$(( $num + 1 )) done - ngctl mkpeer "$iface$bridge:" eiface link$num ether || return + local hook peerhook + case "$NG_TYPE" in + eiface) + # Hook the eiface directly to the bridge + hook=link$num peerhook=ether + ngctl mkpeer "$iface$bridge:" \ + $NG_TYPE $hook $peerhook || return + ;; + iface) + # Hook tcpmss<->iface to bridge + hook=link$num peerhook=bridge + ngctl mkpeer "$iface$bridge:" \ + tcpmss $hook $peerhook || return + hook=iface peerhook=inet + ngctl mkpeer "$iface$bridge:link$num" \ + $NG_TYPE $hook $peerhook || return + ;; + *) return $FAILURE + esac # Rename the new interface - while [ ${#eiface} -gt 15 ]; do # OS limitation - eiface=${eiface%?} + while [ ${#jiface} -gt 15 ]; do # OS limitation + jiface=${jiface%?} done - new=$( set -- `ngctl show -n "$iface$bridge:link$num"` && - echo $2 ) || return - ngctl name "$iface$bridge:link$num" $eiface || return - ifconfig $new name $eiface || return - ifconfig $eiface up || return + case "$NG_TYPE" in + eiface) + new=$( ngctl show -n "$iface$bridge:link$num" ) || + return + new=$( set -- $new; echo $2 ) + ngctl name "$iface$bridge:link$num" $jiface || return + ;; + iface) + ngctl name "$iface$bridge:link$num" $jiface-mss || + return + new=$( ngctl show -n "$jiface-mss:$hook" ) || return + new=$( set -- $new; echo $2 ) + ngctl name $jiface-mss:$hook $jiface || return + ngctl msg $jiface: broadcast || return + ngctl msg $jiface-mss: config "$NG_TCPMSS_CONFIG" || + return + ;; + esac + ifconfig $new name $jiface || return + ifconfig $jiface up || return # # Set the MAC address of the new interface using a sensible # algorithm to prevent conflicts on the network. # - eiface_devid= + jiface_devid= if [ "$clone_mac" ]; then - eiface_devid=$( ifconfig $iface ether | + jiface_devid=$( ifconfig $iface ether | awk '/ether/,$0=$2' ) elif [ ! "$no_derive" ]; then - derive_mac $iface "$name" eiface_devid + derive_mac $iface "$name" jiface_devid fi - [ "$eiface_devid" ] && - ifconfig $eiface ether $eiface_devid > /dev/null 2>&1 + [ "$jiface_devid" ] && + quietly ifconfig $jiface ether $jiface_devid i=$(( $i + 1 )) done # for iface } -jng_graph_usage="graph [-f] [-T type] [-o output]" +jng_graph_usage="graph [-fh] [-T type] [-o output]" jng_graph_descr="Generate network graph (default output is \`jng.svg')" jng_graph() { local OPTIND=1 OPTARG flag local output=jng.svg output_type= force= - while getopts fo:T: flag; do + while getopts fho:T: flag; do case "$flag" in f) force=1 ;; o) output="$OPTARG" ;; @@ -381,8 +456,11 @@ esac done shift $(( $OPTIND - 1 )) - [ $# -eq 0 -a "$output" ] || action_usage graph # NOTREACHED + + [ $# -eq 0 ] || action_usage graph "too many arguments" # NOTREACHED + mustberoot_to_continue + if [ -e "$output" -a ! "$force" ]; then echo "$output: Already exists (use \`-f' to overwrite)" >&2 return $FAILURE @@ -399,21 +477,25 @@ ngctl dot | dot ${output_type:+-T "$output_type"} -o "$output" } -jng_show_usage="show" +jng_show_usage="show [-h]" jng_show_descr="List possible NAME values for \`show NAME'" -jng_show1_usage="show NAME" +jng_show1_usage="show [-h] NAME ..." jng_show1_descr="Lists ng0_NAME [ng1_NAME ...]" -jng_show2_usage="show [NAME]" +jng_show2_usage="show [NAME ...]" +jng_show2_descr="List NAME values or show interfaces associated with NAME." jng_show() { local OPTIND=1 OPTARG flag - while getopts "" flag; do + local name + while getopts h flag; do case "$flag" in *) action_usage show2 # NOTREACHED esac done shift $(( $OPTIND - 1 )) + mustberoot_to_continue + if [ $# -eq 0 ]; then ngctl ls | awk '$4=="bridge",$0=$2' | xargs -rn1 -Ibridge ngctl show bridge: | @@ -421,69 +503,165 @@ sort -u return fi - ngctl ls | awk -v name="$1" ' - match($2, /^ng[[:digit:]]+_/) && - substr($2, RSTART + RLENGTH) == name && - $4 == "eiface", $0 = $2 - ' | sort + for name in "$@"; do + ngctl ls | awk -v name="$name" ' + BEGIN { N = length(name) + 1 } + !match(ng = $2, /^ng[[:digit:]]+_/) { next } + { _name = substr(ng, S = RSTART + RLENGTH) } + _name != name && substr(_name, 1, N) != name "-" { next } + (type = $4) ~ /^(e?iface|tcpmss)$/, $0 = ng + ' | sort + done } -jng_shutdown_usage="shutdown NAME" +jng_shutdown_usage="shutdown [-h] NAME ..." jng_shutdown_descr="Shutdown ng0_NAME [ng1_NAME ...]" jng_shutdown() { local OPTIND=1 OPTARG flag - while getopts "" flag; do + while getopts h flag; do case "$flag" in *) action_usage shutdown # NOTREACHED esac done shift $(( $OPTIND -1 )) - local name="$1" - [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] || - action_usage shutdown # NOTREACHED + + [ $# -gt 0 ] || action_usage shutdown "too few arguments" # NOTREACHED + mustberoot_to_continue - jng_show "$name" | xargs -rn1 -I eiface ngctl shutdown eiface: + + local name + for name in "$@"; do + [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" ] || + action_usage shutdown "invalid name: %s" "$name" + # NOTREACHED + jng_show "$name" | xargs -rn1 -I jiface ngctl shutdown jiface: + done } -jng_stats_usage="stats NAME" +jng_stats_usage="stats [-h] {-a | NAME ...}" jng_stats_descr="Show ng_bridge link statistics for NAME interfaces" jng_stats() { local OPTIND=1 OPTARG flag - while getopts "" flag; do + local show_all= + local name iface ether= + while getopts ahj flag; do case "$flag" in + a) show_all=1 ;; + j) STATS_FMT=json + export pgm + : "${HOSTNAME:=$( hostname )}" + export HOSTNAME + ;; *) action_usage stats # NOTREACHED esac done shift $(( $OPTIND -1 )) - local name="$1" - [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] || - action_usage stats # NOTREACHED + if [ "$show_all" ]; then + [ $# -eq 0 ] || + action_usage stats "too many arguments" # NOTREACHED + + # Get a list of bridged ng_ether(4) devices + for iface in $( ifconfig -l ); do + quietly ngctl info ${iface}bridge: || continue + ether="$ether $iface" + done + set -- $ether $( "$0" show ) + [ $# -gt 0 ] || + action_usage stats "no bridged interfaces" # NOTREACHED + else + [ $# -gt 0 ] || + action_usage stats "too few arguments" # NOTREACHED + fi + mustberoot_to_continue - for eiface in $( jng_show "$name" ); do - echo "$eiface:" - ngctl show $eiface: | awk ' - $3 == "bridge" && $5 ~ /^link/ { - bridge = $2 - link = substr($5, 5) - system(sprintf("ngctl msg %s: getstats %u", - bridge, link)) - }' | fmt 2 | awk ' - /=/ && fl = index($0, "=") { - printf "%20s = %s\n", - substr($0, 0, fl-1), - substr($0, 0, fl+1) - } - ' # END-QUOTE + + local now="$( date +%s )" + for name in "$@"; do + [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" ] || + action_usage stats "invalid name: %s" "$name" + # NOTREACHED + if ifconfig -l | xargs -n1 2> /dev/null | fgrep -qw "$name" + then + [ "$STATS_FMT" != "text" ] || + echo "${name}bridge:link0 [lower]" + ngctl msg ${name}bridge: getstats 0 | + fmt_stats -n "${name}.lower" -t "$now" + + [ "$STATS_FMT" != "text" ] || + echo "${name}bridge:link0 [lower]" + ngctl msg ${name}bridge: getstats 1 | + fmt_stats -n "${name}.upper" -t "$now" + fi + local jiface + for jiface in $( jng_show "$name" ); do + [ "$STATS_FMT" != "text" ] || echo "$jiface:" + ngctl show $jiface: | awk ' + $3 == "bridge" && $5 ~ /^link/ { + bridge = $2 + link = substr($5, 5) + system(sprintf("ngctl msg %s: getstats %u", + bridge, link)) + }' | fmt_stats -n "$jiface" -t "$now" + done done } +fmt_stats() +{ + local OPTIND=1 OPTARG flag + while getopts n:t: flag; do + case "$flag" in + n) name="$OPTARG" ;; + t) time="$OPTARG" ;; + *) break + esac + done + shift $(( OPTIND - 1 )) + fmt 2 | awk -v fmt="$STATS_FMT" -v name="$name" -v tm="$time" ' + function json_add_str(pre, k, s) + { + return sprintf("%s,\"%s\":\"%s\"", pre, k, s) + } + function json_add_int(pre, k, i) + { + return sprintf("%s,\"%s\":%d", pre, k, i) + } + BEGIN { + if (fmt == "json") { + if (time == "") srand() # Time-seed + js = json_add_int(js, "epoch", + time != "" ? time : srand()) + js = json_add_str(js, "hostname", + ENVIRON["HOSTNAME"]) + js = json_add_str(js, "program", + ENVIRON["pgm"]) + js = json_add_str(js, "name", name) + } + } + /=/ && fl = index($0, "=") { + key = substr($0, 0, fl-1) + val = substr($0, fl+1) + if (fmt == "json") { + js = json_add_int(js, key, val) + } else { # Multi-line text + printf "%20s = %s\n", key, val + } + } + END { + if (fmt == "json") { + print "{" substr(js, 2) "}" + } + } + ' # END-QUOTE +} ############################################################ MAIN # # Command-line arguments # +[ $# -gt 0 ] || usage "too few arguments" # NOTREACHED action="$1" [ "$action" ] || usage # NOTREACHED