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
This commit is contained in:
+249
-70
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user