git: 8e68f940c1d1 - main - New version of jng (2.0)
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sat, 04 Apr 2026 20:24:59 UTC
The branch main has been updated by dteske:
URL: https://cgit.FreeBSD.org/src/commit/?id=8e68f940c1d19aaf441c56b46583cbd9ab7448de
commit 8e68f940c1d19aaf441c56b46583cbd9ab7448de
Author: Devin Teske <dteske@FreeBSD.org>
AuthorDate: 2026-04-04 19:39:22 +0000
Commit: Devin Teske <dteske@FreeBSD.org>
CommitDate: 2026-04-04 19:39:22 +0000
New version of jng (2.0)
Changes for jng 1.0 -> 2.0 include:
+ Add experimental MSS clamping
+ Add support for ng_bridge(4) NGM_BRIDGE_GET_STATS (getstats)
+ Add JSON formatted ng_bridge(4) statistics (see above) via "jng stats -j <name>"
+ Add error messages
+ Minor refactoring for code readability (read: quietly() function)
+ Rename eiface variables to jiface to clarify as-for jail interface (not ng_eiface(4))
+ Fix missing description for alternate form of "jng show" usage
+ Update "jng show <name>" to accept multiple names (now "jng show <name> …" is allowed)
+ Update "jng shutdown <name>" to accept multiple names (now "jng shutdown <name> …" is allowed)
+ Add "-a" option to "jng stats" (as-in "jng stats -a") to show all ng_bridge(4) stats
+ Update "jng stats <name>" to accept any kind of name (make it easier to use)
+ Add version ident
+ Remove extraneous line in LICENSE section
+ Add -h to usage statements
+ Bump copyright
Reviewed by: jlduran
Differential Revision: https://reviews.freebsd.org/D43516
---
share/examples/jails/jng | 319 ++++++++++++++++++++++++++++++++++++-----------
1 file changed, 249 insertions(+), 70 deletions(-)
diff --git a/share/examples/jails/jng b/share/examples/jails/jng
index 53dc680e6312..7013bd350bc0 100755
--- a/share/examples/jails/jng
+++ b/share/examples/jails/jng
@@ -1,6 +1,6 @@
#!/bin/sh
#-
-# Copyright (c) 2016 Devin Teske
+# Copyright (c) 2016-2024 Devin Teske <dteske@FreeBSD.org>
# 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 @@ pgm="${0##*/}" # Program basename
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 @@ usage()
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 @@ mustberoot_to_continue()
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 @@ jng_bridge()
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 @@ jng_bridge()
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
@@ -318,11 +362,10 @@ jng_bridge()
# 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
@@ -334,48 +377,80 @@ jng_bridge()
# 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 mtu $mtu || 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 mtu $mtu || 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" ;;
@@ -384,8 +459,11 @@ jng_graph()
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
@@ -402,21 +480,25 @@ jng_graph()
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: |
@@ -424,69 +506,166 @@ jng_show()
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 [-hj] {-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
+ local time=
+ 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 (tm == "") srand() # Time-seed
+ js = json_add_int(js, "epoch",
+ tm != "" ? tm : 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