Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 04 Apr 2026 20:24:59 +0000
From:      Devin Teske <dteske@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 8e68f940c1d1 - main - New version of jng (2.0)
Message-ID:  <69d1739b.3891a.2a3a56ee@gitrepo.freebsd.org>

index | next in thread | raw e-mail

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
 


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69d1739b.3891a.2a3a56ee>