jng revision 295441
1295373Sdteske#!/bin/sh 2295373Sdteske#- 3295373Sdteske# Copyright (c) 2016 Devin Teske 4295373Sdteske# All rights reserved. 5295373Sdteske# 6295373Sdteske# Redistribution and use in source and binary forms, with or without 7295373Sdteske# modification, are permitted provided that the following conditions 8295373Sdteske# are met: 9295373Sdteske# 1. Redistributions of source code must retain the above copyright 10295373Sdteske# notice, this list of conditions and the following disclaimer. 11295373Sdteske# 2. Redistributions in binary form must reproduce the above copyright 12295373Sdteske# notice, this list of conditions and the following disclaimer in the 13295373Sdteske# documentation and/or other materials provided with the distribution. 14295373Sdteske# 15295373Sdteske# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16295373Sdteske# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17295373Sdteske# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18295373Sdteske# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19295373Sdteske# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20295373Sdteske# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21295373Sdteske# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22295373Sdteske# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23295373Sdteske# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24295373Sdteske# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25295373Sdteske# SUCH DAMAGE. 26295373Sdteske# 27295373Sdteske# $FreeBSD: head/share/examples/jails/jng 295441 2016-02-09 18:08:40Z dteske $ 28295373Sdteske# 29295373Sdteske############################################################ IDENT(1) 30295373Sdteske# 31295373Sdteske# $Title: netgraph(4) management script for vnet jails $ 32295373Sdteske# 33295373Sdteske############################################################ INFORMATION 34295373Sdteske# 35295373Sdteske# Use this tool with jail.conf(5) (or rc.conf(5) ``legacy'' configuration) to 36295400Sdteske# manage `vnet' interfaces for jails. Designed to automate the creation of vnet 37295400Sdteske# interface(s) during jail `prestart' and destroy said interface(s) during jail 38295400Sdteske# `poststop'. 39295373Sdteske# 40295400Sdteske# In jail.conf(5) format: 41295400Sdteske# 42295373Sdteske# ### BEGIN EXCERPT ### 43295373Sdteske# 44295373Sdteske# xxx { 45295373Sdteske# host.hostname = "xxx.yyy"; 46295373Sdteske# path = "/vm/xxx"; 47295373Sdteske# 48295373Sdteske# # 49295373Sdteske# # NB: Below 2-lines required 50295373Sdteske# # NB: The number of ngN_xxx interfaces should match the number of 51295373Sdteske# # arguments given to `jng bridge xxx' in exec.prestart value. 52295373Sdteske# # 53295373Sdteske# vnet; 54295373Sdteske# vnet.interface = "ng0_xxx ng1_xxx ..."; 55295373Sdteske# 56295373Sdteske# exec.clean; 57295373Sdteske# exec.system_user = "root"; 58295373Sdteske# exec.jail_user = "root"; 59295373Sdteske# 60295373Sdteske# # 61295373Sdteske# # NB: Below 2-lines required 62295373Sdteske# # NB: The number of arguments after `jng bridge xxx' should match 63295373Sdteske# # the number of ngN_xxx arguments in vnet.interface value. 64295373Sdteske# # 65295373Sdteske# exec.prestart += "jng bridge xxx em0 em1 ..."; 66295373Sdteske# exec.poststop += "jng shutdown xxx"; 67295373Sdteske# 68295373Sdteske# # Standard recipe 69295373Sdteske# exec.start += "/bin/sh /etc/rc"; 70295373Sdteske# exec.stop = "/bin/sh /etc/rc.shutdown"; 71295373Sdteske# exec.consolelog = "/var/log/jail_xxx_console.log"; 72295373Sdteske# mount.devfs; 73295373Sdteske# 74295373Sdteske# # Optional (default off) 75295373Sdteske# #allow.mount; 76295373Sdteske# #allow.set_hostname = 1; 77295373Sdteske# #allow.sysvipc = 1; 78295373Sdteske# #devfs_ruleset = "11"; # rule to unhide bpf for DHCP 79295373Sdteske# } 80295373Sdteske# 81295373Sdteske# ### END EXCERPT ### 82295373Sdteske# 83295373Sdteske# In rc.conf(5) ``legacy'' format (used when /etc/jail.conf does not exist): 84295373Sdteske# 85295373Sdteske# ### BEGIN EXCERPT ### 86295373Sdteske# 87295373Sdteske# jail_enable="YES" 88295373Sdteske# jail_list="xxx" 89295373Sdteske# 90295373Sdteske# # 91295373Sdteske# # Global presets for all jails 92295373Sdteske# # 93295373Sdteske# jail_devfs_enable="YES" # mount devfs 94295373Sdteske# 95295373Sdteske# # 96295373Sdteske# # Global options (default off) 97295373Sdteske# # 98295373Sdteske# #jail_mount_enable="YES" # mount /etc/fstab.{name} 99295373Sdteske# #jail_set_hostname_allow="YES" # Allow hostname to change 100295373Sdteske# #jail_sysvipc_allow="YES" # Allow SysV Interprocess Comm. 101295373Sdteske# 102295373Sdteske# # xxx 103295373Sdteske# jail_xxx_hostname="xxx.shxd.cx" # hostname 104295373Sdteske# jail_xxx_rootdir="/vm/xxx" # root directory 105295373Sdteske# jail_xxx_vnet_interfaces="ng0_xxx ng1xxx ..." # vnet interface(s) 106295373Sdteske# jail_xxx_exec_prestart0="jng bridge xxx em0 em1 ..." # bridge interface(s) 107295373Sdteske# jail_xxx_exec_poststop0="jng shutdown xxx" # destroy interface(s) 108295373Sdteske# #jail_xxx_mount_enable="YES" # mount /etc/fstab.xxx 109295373Sdteske# #jail_xxx_devfs_ruleset="11" # rule to unhide bpf for DHCP 110295373Sdteske# 111295373Sdteske# ### END EXCERPT ### 112295373Sdteske# 113295373Sdteske# Note that the legacy rc.conf(5) format is converted to 114295373Sdteske# /var/run/jail.{name}.conf by /etc/rc.d/jail if jail.conf(5) is missing. 115295373Sdteske# 116295373Sdteske# ASIDE: dhclient(8) inside a vnet jail... 117295373Sdteske# 118295373Sdteske# To allow dhclient(8) to work inside a vnet jail, make sure the following 119295373Sdteske# appears in /etc/devfs.rules (which should be created if it doesn't exist): 120295373Sdteske# 121295373Sdteske# [devfsrules_jail=11] 122295373Sdteske# add include $devfsrules_hide_all 123295373Sdteske# add include $devfsrules_unhide_basic 124295373Sdteske# add include $devfsrules_unhide_login 125295441Sdteske# add path 'bpf*' unhide 126295373Sdteske# 127295373Sdteske# And set ether devfs.ruleset="11" (jail.conf(5)) or 128295373Sdteske# jail_{name}_devfs_ruleset="11" (rc.conf(5)). 129295373Sdteske# 130295373Sdteske# NB: While this tool can't create every type of desirable topology, it should 131295373Sdteske# handle most setups, minus some which considered exotic or purpose-built. 132295373Sdteske# 133295373Sdteske############################################################ GLOBALS 134295373Sdteske 135295373Sdteskepgm="${0##*/}" # Program basename 136295373Sdteske 137295373Sdteske# 138295373Sdteske# Global exit status 139295373Sdteske# 140295373SdteskeSUCCESS=0 141295373SdteskeFAILURE=1 142295373Sdteske 143295373Sdteske############################################################ FUNCTIONS 144295373Sdteske 145295373Sdteskeusage() 146295373Sdteske{ 147295373Sdteske local action usage descr 148295373Sdteske exec >&2 149295373Sdteske echo "Usage: $pgm action [arguments]" 150295373Sdteske echo "Actions:" 151295373Sdteske for action in \ 152295373Sdteske bridge \ 153295373Sdteske graph \ 154295373Sdteske show \ 155295373Sdteske show1 \ 156295373Sdteske shutdown \ 157295373Sdteske ; do 158295373Sdteske eval usage=\"\$jng_${action}_usage\" 159295373Sdteske [ "$usage" ] || continue 160295373Sdteske eval descr=\"\$jng_${action}_descr\" 161295373Sdteske printf "\t%s\n\t\t%s\n" "$usage" "$descr" 162295373Sdteske done 163295373Sdteske exit $FAILURE 164295373Sdteske} 165295373Sdteske 166295373Sdteskeaction_usage() 167295373Sdteske{ 168295373Sdteske local usage action="$1" 169295373Sdteske eval usage=\"\$jng_${action}_usage\" 170295373Sdteske echo "Usage: $pgm $usage" >&2 171295373Sdteske exit $FAILURE 172295373Sdteske} 173295373Sdteske 174295373Sdteskemustberoot_to_continue() 175295373Sdteske{ 176295373Sdteske if [ "$( id -u )" -ne 0 ]; then 177295373Sdteske echo "Must run as root!" >&2 178295373Sdteske exit $FAILURE 179295373Sdteske fi 180295373Sdteske} 181295373Sdteske 182295373Sdteskejng_bridge_usage="bridge [-b BRIDGE_NAME] NAME interface0 [interface1 ...]" 183295373Sdteskejng_bridge_descr="Create ng0_NAME [ng1_NAME ...]" 184295373Sdteskejng_bridge() 185295373Sdteske{ 186295373Sdteske local OPTIND=1 OPTARG flag bridge=bridge 187295373Sdteske while getopts b: flag; do 188295373Sdteske case "$flag" in 189295373Sdteske b) bridge="$OPTARG" 190295373Sdteske [ "$bridge" ] || action_usage bridge ;; # NOTREACHED 191295373Sdteske *) action_usage bridge # NOTREACHED 192295373Sdteske esac 193295373Sdteske done 194295373Sdteske shift $(( $OPTIND - 1 )) 195295373Sdteske 196295373Sdteske local name="$1" 197295373Sdteske [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -gt 1 ] || 198295373Sdteske action_usage bridge # NOTREACHED 199295373Sdteske shift 1 # name 200295373Sdteske 201295373Sdteske mustberoot_to_continue 202295373Sdteske 203295373Sdteske local iface iface_devid eiface eiface_devid 204295373Sdteske local new num quad i=0 205295373Sdteske for iface in $*; do 206295373Sdteske 207295373Sdteske # 0. Make sure the interface doesn't exist already 208295373Sdteske eiface=ng${i}_$name 209295373Sdteske ngctl msg "$eiface:" getifname > /dev/null 2>&1 && continue 210295373Sdteske 211295373Sdteske # 1. Bring the interface up 212295373Sdteske ifconfig $iface up || return 213295373Sdteske 214295373Sdteske # 2. Set promiscuous mode and don't overwrite src addr 215295373Sdteske ngctl msg $iface: setpromisc 1 || return 216295373Sdteske ngctl msg $iface: setautosrc 0 || return 217295373Sdteske 218295373Sdteske # 3. Make sure the interface has been bridged 219295373Sdteske if ! ngctl info ${iface}bridge: > /dev/null 2>&1; then 220295373Sdteske ngctl mkpeer $iface: bridge lower link0 || return 221295373Sdteske ngctl connect $iface: $iface:lower upper link1 || 222295373Sdteske return 223295373Sdteske ngctl name $iface:lower ${iface}bridge || return 224295373Sdteske fi 225295373Sdteske 226295373Sdteske # 3.5. Optionally create a secondary bridge 227295373Sdteske if [ "$bridge" != "bridge" ] && 228295373Sdteske ! ngctl info "$iface$bridge:" > /dev/null 2>&1 229295373Sdteske then 230295373Sdteske num=2 231295373Sdteske while ngctl msg ${iface}bridge: getstats $num \ 232295373Sdteske > /dev/null 2>&1 233295373Sdteske do 234295373Sdteske num=$(( $num + 1 )) 235295373Sdteske done 236295373Sdteske ngctl mkpeer $iface:lower bridge link$num link1 || 237295373Sdteske return 238295373Sdteske ngctl name ${iface}bridge:link$num "$iface$bridge" || 239295373Sdteske return 240295373Sdteske fi 241295373Sdteske 242295373Sdteske # 4. Create a new interface to the bridge 243295373Sdteske num=2 244295373Sdteske while ngctl msg "$iface$bridge:" getstats $num > /dev/null 2>&1 245295373Sdteske do 246295373Sdteske num=$(( $num + 1 )) 247295373Sdteske done 248295373Sdteske ngctl mkpeer "$iface$bridge:" eiface link$num ether || return 249295373Sdteske 250295373Sdteske # 5. Rename the new interface 251295373Sdteske while [ ${#eiface} -gt 15 ]; do # OS limitation 252295373Sdteske eiface=${eiface%?} 253295373Sdteske done 254295373Sdteske new=$( set -- `ngctl show -n "$iface$bridge:link$num"` && 255295373Sdteske echo $2 ) || return 256295373Sdteske ngctl name "$iface$bridge:link$num" $eiface || return 257295373Sdteske ifconfig $new name $eiface || return 258295373Sdteske 259295373Sdteske # 260295373Sdteske # 6. Set the MAC address of the new interface using a sensible 261295373Sdteske # algorithm to prevent conflicts on the network. 262295373Sdteske # 263295422Sdteske # The formula I'm using is ``NP:SS:SS:II:II:II'' where: 264295422Sdteske # + N denotes 4 bits used as a counter to support branching 265295422Sdteske # each parent interface up to 15 times under the same jail 266295422Sdteske # name (see S below). 267295373Sdteske # + P denotes the special nibble whose value, if one of 268295373Sdteske # 2, 6, A, or E (but usually 2) denotes a privately 269295373Sdteske # administered MAC address (while remaining routable). 270295422Sdteske # + S denotes 16 bits, the sum(1) value of the jail name. 271295373Sdteske # + I denotes bits that are inherited from parent interface. 272295373Sdteske # 273295373Sdteske # The S bits are a CRC-16 checksum of NAME, allowing the jail 274295373Sdteske # to change link numbers in ng_bridge(4) without affecting the 275295422Sdteske # MAC address. Meanwhile, if... 276295422Sdteske # + the jail NAME changes (e.g., it was duplicated and given 277295422Sdteske # a new name with no other changes) 278295422Sdteske # + the underlying network interface changes 279295422Sdteske # + the jail is moved to another host 280295422Sdteske # the MAC address will be recalculated to a new, similarly 281295422Sdteske # unique value preventing conflict. 282295373Sdteske # 283295373Sdteske iface_devid=$( ifconfig $iface ether | awk '/ether/,$0=$2' ) 284295422Sdteske eiface_devid=${iface_devid#??:??:??} 285295373Sdteske num=$( set -- `echo -n $name | sum` && echo $1 ) 286295373Sdteske quad=$(( $num & 15 )) 287295373Sdteske case "$quad" in 288295373Sdteske 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 289295373Sdteske 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 290295373Sdteske esac 291295422Sdteske eiface_devid=$quad$eiface_devid 292295373Sdteske num=$(( $num >> 4 )) 293295373Sdteske quad=$(( $num & 15 )) 294295373Sdteske case "$quad" in 295295373Sdteske 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 296295373Sdteske 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 297295373Sdteske esac 298295373Sdteske eiface_devid=$quad$eiface_devid 299295373Sdteske num=$(( $num >> 4 )) 300295373Sdteske quad=$(( $num & 15 )) 301295373Sdteske case "$quad" in 302295373Sdteske 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 303295373Sdteske 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 304295373Sdteske esac 305295422Sdteske eiface_devid=$quad:$eiface_devid 306295422Sdteske num=$(( $num >> 4 )) 307295422Sdteske quad=$(( $num & 15 )) 308295422Sdteske case "$quad" in 309295422Sdteske 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 310295422Sdteske 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 311295422Sdteske esac 312295401Sdteske case "$iface_devid" in 313295401Sdteske ?2:*) eiface_devid=a:$quad$eiface_devid ;; 314295401Sdteske *) eiface_devid=2:$quad$eiface_devid 315295401Sdteske esac 316295422Sdteske eval num=\$_${iface}_num 317295422Sdteske if [ "$num" ]; then 318295422Sdteske num=$(( $num + 1 )) 319295422Sdteske eval _${iface}_num=$num 320295422Sdteske else 321295422Sdteske num=0 322295422Sdteske local _${iface}_num=$num 323295422Sdteske fi 324295373Sdteske quad=$(( $num & 15 )) 325295373Sdteske case "$quad" in 326295373Sdteske 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 327295373Sdteske 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 328295373Sdteske esac 329295373Sdteske eiface_devid=$quad$eiface_devid 330295373Sdteske ifconfig $eiface ether $eiface_devid > /dev/null 2>&1 331295373Sdteske 332295373Sdteske i=$(( $i + 1 )) # on to next ng{i}_name 333295373Sdteske done # for iface 334295373Sdteske} 335295373Sdteske 336295373Sdteskejng_graph_usage="graph [-f] [-T type] [-o output]" 337295373Sdteskejng_graph_descr="Generate network graph (default output is \`jng.svg')" 338295373Sdteskejng_graph() 339295373Sdteske{ 340295373Sdteske local OPTIND=1 OPTARG flag 341295373Sdteske local output=jng.svg output_type= force= 342295373Sdteske while getopts fo:T: flag; do 343295373Sdteske case "$flag" in 344295373Sdteske f) force=1 ;; 345295373Sdteske o) output="$OPTARG" ;; 346295373Sdteske T) output_type="$OPTARG" ;; 347295373Sdteske *) action_usage graph # NOTREACHED 348295373Sdteske esac 349295373Sdteske done 350295373Sdteske shift $(( $OPTIND - 1 )) 351295373Sdteske [ $# -eq 0 -a "$output" ] || action_usage graph # NOTREACHED 352295373Sdteske mustberoot_to_continue 353295373Sdteske if [ -e "$output" -a ! "$force" ]; then 354295373Sdteske echo "$output: Already exists (use \`-f' to overwrite)" >&2 355295373Sdteske return $FAILURE 356295373Sdteske fi 357295373Sdteske if [ ! "$output_type" ]; then 358295373Sdteske local valid suffix 359295373Sdteske valid=$( dot -Txxx 2>&1 ) 360295373Sdteske for suffix in ${valid##*:}; do 361295373Sdteske [ "$output" != "${output%.$suffix}" ] || continue 362295373Sdteske output_type=$suffix 363295373Sdteske break 364295373Sdteske done 365295373Sdteske fi 366295373Sdteske ngctl dot | dot ${output_type:+-T "$output_type"} -o "$output" 367295373Sdteske} 368295373Sdteske 369295373Sdteskejng_show_usage="show" 370295373Sdteskejng_show_descr="List possible NAME values for \`show NAME'" 371295373Sdteskejng_show1_usage="show NAME" 372295373Sdteskejng_show1_descr="Lists ng0_NAME [ng1_NAME ...]" 373295373Sdteskejng_show2_usage="show [NAME]" 374295373Sdteskejng_show() 375295373Sdteske{ 376295373Sdteske local OPTIND=1 OPTARG flag 377295373Sdteske while getopts "" flag; do 378295373Sdteske case "$flag" in 379295373Sdteske *) action_usage show2 # NOTREACHED 380295373Sdteske esac 381295373Sdteske done 382295373Sdteske shift $(( $OPTIND - 1 )) 383295373Sdteske mustberoot_to_continue 384295373Sdteske if [ $# -eq 0 ]; then 385295373Sdteske ngctl ls | awk '$4=="bridge",$0=$2' | 386295373Sdteske xargs -rn1 -Ibridge ngctl show bridge: | 387295373Sdteske awk 'sub(/^ng[[:digit:]]+_/, "", $2), $0 = $2' | 388295373Sdteske sort -u 389295373Sdteske return 390295373Sdteske fi 391295373Sdteske ngctl ls | awk -v name="$1" ' 392295373Sdteske match($2, /^ng[[:digit:]]+_/) && 393295373Sdteske substr($2, RSTART + RLENGTH) == name && 394295373Sdteske $4 == "eiface", $0 = $2 395295373Sdteske ' | sort 396295373Sdteske} 397295373Sdteske 398295373Sdteskejng_shutdown_usage="shutdown NAME" 399295373Sdteskejng_shutdown_descr="Shutdown ng0_NAME [ng1_NAME ...]" 400295373Sdteskejng_shutdown() 401295373Sdteske{ 402295373Sdteske local OPTIND=1 OPTARG flag 403295373Sdteske while getopts "" flag; do 404295373Sdteske case "$flag" in 405295373Sdteske *) action_usage shutdown # NOTREACHED 406295373Sdteske esac 407295373Sdteske done 408295373Sdteske shift $(( $OPTIND -1 )) 409295373Sdteske local name="$1" 410295373Sdteske [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] || 411295373Sdteske action_usage shutdown # NOTREACHED 412295373Sdteske mustberoot_to_continue 413295373Sdteske jng_show "$name" | xargs -rn1 -I eiface ngctl shutdown eiface: 414295373Sdteske} 415295373Sdteske 416295373Sdteske############################################################ MAIN 417295373Sdteske 418295373Sdteske# 419295373Sdteske# Command-line arguments 420295373Sdteske# 421295373Sdteskeaction="$1" 422295373Sdteske[ "$action" ] || usage # NOTREACHED 423295373Sdteske 424295373Sdteske# 425295373Sdteske# Validate action argument 426295373Sdteske# 427295373Sdteskeif [ "$BASH_VERSION" ]; then 428295373Sdteske type="$( type -t "jng_$action" )" || usage # NOTREACHED 429295373Sdteskeelse 430295373Sdteske type="$( type "jng_$action" 2> /dev/null )" || usage # NOTREACHED 431295373Sdteskefi 432295373Sdteskecase "$type" in 433295373Sdteske*function) 434295373Sdteske shift 1 # action 435295373Sdteske eval "jng_$action" \"\$@\" 436295373Sdteske ;; 437295373Sdteske*) usage # NOTREACHED 438295373Sdteskeesac 439295373Sdteske 440295373Sdteske################################################################################ 441295373Sdteske# END 442295373Sdteske################################################################################ 443