3 # Copyright (c) 2016 Devin Teske
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
15 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 ############################################################ IDENT(1)
31 # $Title: netgraph(4) management script for vnet jails $
33 ############################################################ INFORMATION
35 # Use this tool with jail.conf(5) (or rc.conf(5) ``legacy'' configuration) to
36 # manage `vnet' interfaces for jails. Designed to automate the creation of vnet
37 # interface(s) during jail `prestart' and destroy said interface(s) during jail
40 # In jail.conf(5) format:
42 # ### BEGIN EXCERPT ###
45 # host.hostname = "xxx.yyy";
49 # # NB: Below 2-lines required
50 # # NB: The number of ngN_xxx interfaces should match the number of
51 # # arguments given to `jng bridge xxx' in exec.prestart value.
54 # vnet.interface = ng0_xxx, ng1_xxx, ...;
57 # exec.system_user = "root";
58 # exec.jail_user = "root";
61 # # NB: Below 2-lines required
62 # # NB: The number of arguments after `jng bridge xxx' should match
63 # # the number of ngN_xxx arguments in vnet.interface value.
65 # exec.prestart += "jng bridge xxx em0 em1 ...";
66 # exec.poststop += "jng shutdown xxx";
69 # exec.start += "/bin/sh /etc/rc";
70 # exec.stop = "/bin/sh /etc/rc.shutdown";
71 # exec.consolelog = "/var/log/jail_xxx_console.log";
74 # # Optional (default off)
76 # #allow.set_hostname = 1;
78 # #devfs_ruleset = "11"; # rule to unhide bpf for DHCP
83 # In rc.conf(5) ``legacy'' format (used when /etc/jail.conf does not exist):
85 # ### BEGIN EXCERPT ###
91 # # Global presets for all jails
93 # jail_devfs_enable="YES" # mount devfs
96 # # Global options (default off)
98 # #jail_mount_enable="YES" # mount /etc/fstab.{name}
99 # #jail_set_hostname_allow="YES" # Allow hostname to change
100 # #jail_sysvipc_allow="YES" # Allow SysV Interprocess Comm.
103 # jail_xxx_hostname="xxx.shxd.cx" # hostname
104 # jail_xxx_rootdir="/vm/xxx" # root directory
105 # jail_xxx_vnet_interfaces="ng0_xxx ng1xxx ..." # vnet interface(s)
106 # jail_xxx_exec_prestart0="jng bridge xxx em0 em1 ..." # bridge interface(s)
107 # jail_xxx_exec_poststop0="jng shutdown xxx" # destroy interface(s)
108 # #jail_xxx_mount_enable="YES" # mount /etc/fstab.xxx
109 # #jail_xxx_devfs_ruleset="11" # rule to unhide bpf for DHCP
111 # ### END EXCERPT ###
113 # Note that the legacy rc.conf(5) format is converted to
114 # /var/run/jail.{name}.conf by /etc/rc.d/jail if jail.conf(5) is missing.
116 # ASIDE: dhclient(8) inside a vnet jail...
118 # To allow dhclient(8) to work inside a vnet jail, make sure the following
119 # appears in /etc/devfs.rules (which should be created if it doesn't exist):
121 # [devfsrules_jail=11]
122 # add include $devfsrules_hide_all
123 # add include $devfsrules_unhide_basic
124 # add include $devfsrules_unhide_login
125 # add path 'bpf*' unhide
127 # And set ether devfs.ruleset="11" (jail.conf(5)) or
128 # jail_{name}_devfs_ruleset="11" (rc.conf(5)).
130 # NB: While this tool can't create every type of desirable topology, it should
131 # handle most setups, minus some which considered exotic or purpose-built.
133 ############################################################ GLOBALS
135 pgm="${0##*/}" # Program basename
143 ############################################################ FUNCTIONS
147 local action usage descr
149 echo "Usage: $pgm action [arguments]"
159 eval usage=\"\$jng_${action}_usage\"
160 [ "$usage" ] || continue
161 eval descr=\"\$jng_${action}_descr\"
162 printf "\t%s\n\t\t%s\n" "$usage" "$descr"
169 local usage descr action="$1"
170 eval usage=\"\$jng_${action}_usage\"
171 echo "Usage: $pgm $usage" >&2
172 eval descr=\"\$jng_${action}_descr\"
173 printf "\t%s\n" "$descr"
179 local OPTIND=1 OPTARG __flag
180 local __mac_num= __make_pair=
181 while getopts 2n: __flag; do
184 n) __mac_num=${OPTARG%%[^0-9]*} ;;
187 shift $(( $OPTIND - 1 ))
189 if [ ! "$__mac_num" ]; then
190 eval __mac_num=\${_${iface}_num:--1}
191 __mac_num=$(( $__mac_num + 1 ))
192 eval _${iface}_num=\$__mac_num
195 local __iface="$1" __name="$2" __var_to_set="$3" __var_to_set_b="$4"
196 local __iface_devid __new_devid __num __new_devid_b
198 # Calculate MAC address derived from given iface.
200 # The formula I'm using is ``NP:SS:SS:II:II:II'' where:
201 # + N denotes 4 bits used as a counter to support branching
202 # each parent interface up to 15 times under the same jail
203 # name (see S below).
204 # + P denotes the special nibble whose value, if one of
205 # 2, 6, A, or E (but usually 2) denotes a privately
206 # administered MAC address (while remaining routable).
207 # + S denotes 16 bits, the sum(1) value of the jail name.
208 # + I denotes bits that are inherited from parent interface.
210 # The S bits are a CRC-16 checksum of NAME, allowing the jail
211 # to change link numbers in ng_bridge(4) without affecting the
212 # MAC address. Meanwhile, if...
213 # + the jail NAME changes (e.g., it was duplicated and given
214 # a new name with no other changes)
215 # + the underlying network interface changes
216 # + the jail is moved to another host
217 # the MAC address will be recalculated to a new, similarly
218 # unique value preventing conflict.
220 __iface_devid=$( ifconfig $__iface ether | awk '/ether/,$0=$2' )
222 __new_devid=${__iface_devid#??:??:??} # => :II:II:II
224 __num=$( set -- `echo -n "$__name" | sum` && echo $1 )
225 __new_devid=$( printf :%02x:%02x \
226 $(( $__num >> 8 & 255 )) $(( $__num & 255 )) )$__new_devid
227 # => P:SS:SS:II:II:II
228 case "$__iface_devid" in
229 ?2:*) __new_devid=a$__new_devid __new_devid_b=e$__new_devid ;;
230 ?[Ee]:*) __new_devid=2$__new_devid __new_devid_b=6$__new_devid ;;
231 *) __new_devid=2$__new_devid __new_devid_b=e$__new_devid
233 # => NP:SS:SS:II:II:II
234 __new_devid=$( printf %x $(( $__mac_num & 15 )) )$__new_devid
235 __new_devid_b=$( printf %x $(( $__mac_num & 15 )) )$__new_devid_b
238 # Return derivative MAC address(es)
240 if [ "$__make_pair" ]; then
241 if [ "$__var_to_set" -a "$__var_to_set_b" ]; then
242 eval $__var_to_set=\$__new_devid
243 eval $__var_to_set_b=\$__new_devid_b
245 echo $__new_devid $__new_devid_b
248 if [ "$__var_to_set" ]; then
249 eval $__var_to_set=\$__new_devid
256 mustberoot_to_continue()
258 if [ "$( id -u )" -ne 0 ]; then
259 echo "Must run as root!" >&2
264 jng_bridge_usage="bridge [-b BRIDGE_NAME] NAME [!|=]iface0 [[!|=]iface1 ...]"
265 jng_bridge_descr="Create ng0_NAME [ng1_NAME ...]"
268 local OPTIND=1 OPTARG flag bridge=bridge
269 while getopts b: flag; do
272 [ "$bridge" ] || action_usage bridge ;; # NOTREACHED
273 *) action_usage bridge # NOTREACHED
276 shift $(( $OPTIND - 1 ))
279 [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -gt 1 ] ||
280 action_usage bridge # NOTREACHED
283 mustberoot_to_continue
285 local iface parent eiface eiface_devid
286 local new clone_mac no_derive num quad i=0
292 =*) iface=${iface#=} clone_mac=1 ;;
293 !*) iface=${iface#!} no_derive=1 ;;
296 # Make sure the interface doesn't exist already
298 if ngctl msg "$eiface:" getifname > /dev/null 2>&1; then
303 # Bring the interface up
304 ifconfig $iface up || return
306 # Set promiscuous mode and don't overwrite src addr
307 ngctl msg $iface: setpromisc 1 || return
308 ngctl msg $iface: setautosrc 0 || return
310 # Make sure the interface has been bridged
311 if ! ngctl info ${iface}bridge: > /dev/null 2>&1; then
312 ngctl mkpeer $iface: bridge lower link0 || return
313 ngctl connect $iface: $iface:lower upper link1 ||
315 ngctl name $iface:lower ${iface}bridge || return
318 # Optionally create a secondary bridge
319 if [ "$bridge" != "bridge" ] &&
320 ! ngctl info "$iface$bridge:" > /dev/null 2>&1
323 while ngctl msg ${iface}bridge: getstats $num \
328 ngctl mkpeer $iface:lower bridge link$num link1 ||
330 ngctl name ${iface}bridge:link$num "$iface$bridge" ||
334 # Create a new interface to the bridge
336 while ngctl msg "$iface$bridge:" getstats $num > /dev/null 2>&1
340 ngctl mkpeer "$iface$bridge:" eiface link$num ether || return
342 # Rename the new interface
343 while [ ${#eiface} -gt 15 ]; do # OS limitation
346 new=$( set -- `ngctl show -n "$iface$bridge:link$num"` &&
348 ngctl name "$iface$bridge:link$num" $eiface || return
349 ifconfig $new name $eiface || return
350 ifconfig $eiface up || return
353 # Set the MAC address of the new interface using a sensible
354 # algorithm to prevent conflicts on the network.
357 if [ "$clone_mac" ]; then
358 eiface_devid=$( ifconfig $iface ether |
359 awk '/ether/,$0=$2' )
360 elif [ ! "$no_derive" ]; then
361 derive_mac $iface "$name" eiface_devid
363 [ "$eiface_devid" ] &&
364 ifconfig $eiface ether $eiface_devid > /dev/null 2>&1
370 jng_graph_usage="graph [-f] [-T type] [-o output]"
371 jng_graph_descr="Generate network graph (default output is \`jng.svg')"
374 local OPTIND=1 OPTARG flag
375 local output=jng.svg output_type= force=
376 while getopts fo:T: flag; do
379 o) output="$OPTARG" ;;
380 T) output_type="$OPTARG" ;;
381 *) action_usage graph # NOTREACHED
384 shift $(( $OPTIND - 1 ))
385 [ $# -eq 0 -a "$output" ] || action_usage graph # NOTREACHED
386 mustberoot_to_continue
387 if [ -e "$output" -a ! "$force" ]; then
388 echo "$output: Already exists (use \`-f' to overwrite)" >&2
391 if [ ! "$output_type" ]; then
393 valid=$( dot -Txxx 2>&1 )
394 for suffix in ${valid##*:}; do
395 [ "$output" != "${output%.$suffix}" ] || continue
400 ngctl dot | dot ${output_type:+-T "$output_type"} -o "$output"
403 jng_show_usage="show"
404 jng_show_descr="List possible NAME values for \`show NAME'"
405 jng_show1_usage="show NAME"
406 jng_show1_descr="Lists ng0_NAME [ng1_NAME ...]"
407 jng_show2_usage="show [NAME]"
410 local OPTIND=1 OPTARG flag
411 while getopts "" flag; do
413 *) action_usage show2 # NOTREACHED
416 shift $(( $OPTIND - 1 ))
417 mustberoot_to_continue
418 if [ $# -eq 0 ]; then
419 ngctl ls | awk '$4=="bridge",$0=$2' |
420 xargs -rn1 -Ibridge ngctl show bridge: |
421 awk 'sub(/^ng[[:digit:]]+_/, "", $2), $0 = $2' |
425 ngctl ls | awk -v name="$1" '
426 match($2, /^ng[[:digit:]]+_/) &&
427 substr($2, RSTART + RLENGTH) == name &&
428 $4 == "eiface", $0 = $2
432 jng_shutdown_usage="shutdown NAME"
433 jng_shutdown_descr="Shutdown ng0_NAME [ng1_NAME ...]"
436 local OPTIND=1 OPTARG flag
437 while getopts "" flag; do
439 *) action_usage shutdown # NOTREACHED
442 shift $(( $OPTIND -1 ))
444 [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] ||
445 action_usage shutdown # NOTREACHED
446 mustberoot_to_continue
447 jng_show "$name" | xargs -rn1 -I eiface ngctl shutdown eiface:
450 jng_stats_usage="stats NAME"
451 jng_stats_descr="Show ng_bridge link statistics for NAME interfaces"
454 local OPTIND=1 OPTARG flag
455 while getopts "" flag; do
457 *) action_usage stats # NOTREACHED
460 shift $(( $OPTIND -1 ))
462 [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] ||
463 action_usage stats # NOTREACHED
464 mustberoot_to_continue
465 for eiface in $( jng_show "$name" ); do
467 ngctl show $eiface: | awk '
468 $3 == "bridge" && $5 ~ /^link/ {
471 system(sprintf("ngctl msg %s: getstats %u",
474 /=/ && fl = index($0, "=") {
475 printf "%20s = %s\n",
483 ############################################################ MAIN
486 # Command-line arguments
489 [ "$action" ] || usage # NOTREACHED
492 # Validate action argument
494 if [ "$BASH_VERSION" ]; then
495 type="$( type -t "jng_$action" )" || usage # NOTREACHED
497 type="$( type "jng_$action" 2> /dev/null )" || usage # NOTREACHED
502 eval "jng_$action" \"\$@\"
504 *) usage # NOTREACHED
507 ################################################################################
509 ################################################################################