jng revision 295460
1#!/bin/sh 2#- 3# Copyright (c) 2016 Devin Teske 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 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. 14# 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 25# SUCH DAMAGE. 26# 27# $FreeBSD: head/share/examples/jails/jng 295460 2016-02-10 04:56:38Z dteske $ 28# 29############################################################ IDENT(1) 30# 31# $Title: netgraph(4) management script for vnet jails $ 32# 33############################################################ INFORMATION 34# 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 38# `poststop'. 39# 40# In jail.conf(5) format: 41# 42# ### BEGIN EXCERPT ### 43# 44# xxx { 45# host.hostname = "xxx.yyy"; 46# path = "/vm/xxx"; 47# 48# # 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. 52# # 53# vnet; 54# vnet.interface = "ng0_xxx ng1_xxx ..."; 55# 56# exec.clean; 57# exec.system_user = "root"; 58# exec.jail_user = "root"; 59# 60# # 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. 64# # 65# exec.prestart += "jng bridge xxx em0 em1 ..."; 66# exec.poststop += "jng shutdown xxx"; 67# 68# # Standard recipe 69# exec.start += "/bin/sh /etc/rc"; 70# exec.stop = "/bin/sh /etc/rc.shutdown"; 71# exec.consolelog = "/var/log/jail_xxx_console.log"; 72# mount.devfs; 73# 74# # Optional (default off) 75# #allow.mount; 76# #allow.set_hostname = 1; 77# #allow.sysvipc = 1; 78# #devfs_ruleset = "11"; # rule to unhide bpf for DHCP 79# } 80# 81# ### END EXCERPT ### 82# 83# In rc.conf(5) ``legacy'' format (used when /etc/jail.conf does not exist): 84# 85# ### BEGIN EXCERPT ### 86# 87# jail_enable="YES" 88# jail_list="xxx" 89# 90# # 91# # Global presets for all jails 92# # 93# jail_devfs_enable="YES" # mount devfs 94# 95# # 96# # Global options (default off) 97# # 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. 101# 102# # xxx 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 110# 111# ### END EXCERPT ### 112# 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. 115# 116# ASIDE: dhclient(8) inside a vnet jail... 117# 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): 120# 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 126# 127# And set ether devfs.ruleset="11" (jail.conf(5)) or 128# jail_{name}_devfs_ruleset="11" (rc.conf(5)). 129# 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. 132# 133############################################################ GLOBALS 134 135pgm="${0##*/}" # Program basename 136 137# 138# Global exit status 139# 140SUCCESS=0 141FAILURE=1 142 143############################################################ FUNCTIONS 144 145usage() 146{ 147 local action usage descr 148 exec >&2 149 echo "Usage: $pgm action [arguments]" 150 echo "Actions:" 151 for action in \ 152 bridge \ 153 graph \ 154 show \ 155 show1 \ 156 shutdown \ 157 stats \ 158 ; do 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" 163 done 164 exit $FAILURE 165} 166 167action_usage() 168{ 169 local usage action="$1" 170 eval usage=\"\$jng_${action}_usage\" 171 echo "Usage: $pgm $usage" >&2 172 exit $FAILURE 173} 174 175mustberoot_to_continue() 176{ 177 if [ "$( id -u )" -ne 0 ]; then 178 echo "Must run as root!" >&2 179 exit $FAILURE 180 fi 181} 182 183jng_bridge_usage="bridge [-b BRIDGE_NAME] NAME interface0 [interface1 ...]" 184jng_bridge_descr="Create ng0_NAME [ng1_NAME ...]" 185jng_bridge() 186{ 187 local OPTIND=1 OPTARG flag bridge=bridge 188 while getopts b: flag; do 189 case "$flag" in 190 b) bridge="$OPTARG" 191 [ "$bridge" ] || action_usage bridge ;; # NOTREACHED 192 *) action_usage bridge # NOTREACHED 193 esac 194 done 195 shift $(( $OPTIND - 1 )) 196 197 local name="$1" 198 [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -gt 1 ] || 199 action_usage bridge # NOTREACHED 200 shift 1 # name 201 202 mustberoot_to_continue 203 204 local iface iface_devid eiface eiface_devid 205 local new num quad i=0 206 for iface in $*; do 207 208 # 0. Make sure the interface doesn't exist already 209 eiface=ng${i}_$name 210 ngctl msg "$eiface:" getifname > /dev/null 2>&1 && continue 211 212 # 1. Bring the interface up 213 ifconfig $iface up || return 214 215 # 2. Set promiscuous mode and don't overwrite src addr 216 ngctl msg $iface: setpromisc 1 || return 217 ngctl msg $iface: setautosrc 0 || return 218 219 # 3. Make sure the interface has been bridged 220 if ! ngctl info ${iface}bridge: > /dev/null 2>&1; then 221 ngctl mkpeer $iface: bridge lower link0 || return 222 ngctl connect $iface: $iface:lower upper link1 || 223 return 224 ngctl name $iface:lower ${iface}bridge || return 225 fi 226 227 # 3.5. Optionally create a secondary bridge 228 if [ "$bridge" != "bridge" ] && 229 ! ngctl info "$iface$bridge:" > /dev/null 2>&1 230 then 231 num=2 232 while ngctl msg ${iface}bridge: getstats $num \ 233 > /dev/null 2>&1 234 do 235 num=$(( $num + 1 )) 236 done 237 ngctl mkpeer $iface:lower bridge link$num link1 || 238 return 239 ngctl name ${iface}bridge:link$num "$iface$bridge" || 240 return 241 fi 242 243 # 4. Create a new interface to the bridge 244 num=2 245 while ngctl msg "$iface$bridge:" getstats $num > /dev/null 2>&1 246 do 247 num=$(( $num + 1 )) 248 done 249 ngctl mkpeer "$iface$bridge:" eiface link$num ether || return 250 251 # 5. Rename the new interface 252 while [ ${#eiface} -gt 15 ]; do # OS limitation 253 eiface=${eiface%?} 254 done 255 new=$( set -- `ngctl show -n "$iface$bridge:link$num"` && 256 echo $2 ) || return 257 ngctl name "$iface$bridge:link$num" $eiface || return 258 ifconfig $new name $eiface || return 259 ifconfig $eiface up || return 260 261 # 262 # 6. Set the MAC address of the new interface using a sensible 263 # algorithm to prevent conflicts on the network. 264 # 265 # The formula I'm using is ``NP:SS:SS:II:II:II'' where: 266 # + N denotes 4 bits used as a counter to support branching 267 # each parent interface up to 15 times under the same jail 268 # name (see S below). 269 # + P denotes the special nibble whose value, if one of 270 # 2, 6, A, or E (but usually 2) denotes a privately 271 # administered MAC address (while remaining routable). 272 # + S denotes 16 bits, the sum(1) value of the jail name. 273 # + I denotes bits that are inherited from parent interface. 274 # 275 # The S bits are a CRC-16 checksum of NAME, allowing the jail 276 # to change link numbers in ng_bridge(4) without affecting the 277 # MAC address. Meanwhile, if... 278 # + the jail NAME changes (e.g., it was duplicated and given 279 # a new name with no other changes) 280 # + the underlying network interface changes 281 # + the jail is moved to another host 282 # the MAC address will be recalculated to a new, similarly 283 # unique value preventing conflict. 284 # 285 iface_devid=$( ifconfig $iface ether | awk '/ether/,$0=$2' ) 286 eiface_devid=${iface_devid#??:??:??} 287 num=$( set -- `echo -n $name | sum` && echo $1 ) 288 quad=$(( $num & 15 )) 289 case "$quad" in 290 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 291 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 292 esac 293 eiface_devid=$quad$eiface_devid 294 num=$(( $num >> 4 )) 295 quad=$(( $num & 15 )) 296 case "$quad" in 297 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 298 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 299 esac 300 eiface_devid=$quad$eiface_devid 301 num=$(( $num >> 4 )) 302 quad=$(( $num & 15 )) 303 case "$quad" in 304 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 305 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 306 esac 307 eiface_devid=$quad:$eiface_devid 308 num=$(( $num >> 4 )) 309 quad=$(( $num & 15 )) 310 case "$quad" in 311 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 312 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 313 esac 314 case "$iface_devid" in 315 ?2:*) eiface_devid=a:$quad$eiface_devid ;; 316 *) eiface_devid=2:$quad$eiface_devid 317 esac 318 eval num=\$_${iface}_num 319 if [ "$num" ]; then 320 num=$(( $num + 1 )) 321 eval _${iface}_num=$num 322 else 323 num=0 324 local _${iface}_num=$num 325 fi 326 quad=$(( $num & 15 )) 327 case "$quad" in 328 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 329 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 330 esac 331 eiface_devid=$quad$eiface_devid 332 ifconfig $eiface ether $eiface_devid > /dev/null 2>&1 333 334 i=$(( $i + 1 )) # on to next ng{i}_name 335 done # for iface 336} 337 338jng_graph_usage="graph [-f] [-T type] [-o output]" 339jng_graph_descr="Generate network graph (default output is \`jng.svg')" 340jng_graph() 341{ 342 local OPTIND=1 OPTARG flag 343 local output=jng.svg output_type= force= 344 while getopts fo:T: flag; do 345 case "$flag" in 346 f) force=1 ;; 347 o) output="$OPTARG" ;; 348 T) output_type="$OPTARG" ;; 349 *) action_usage graph # NOTREACHED 350 esac 351 done 352 shift $(( $OPTIND - 1 )) 353 [ $# -eq 0 -a "$output" ] || action_usage graph # NOTREACHED 354 mustberoot_to_continue 355 if [ -e "$output" -a ! "$force" ]; then 356 echo "$output: Already exists (use \`-f' to overwrite)" >&2 357 return $FAILURE 358 fi 359 if [ ! "$output_type" ]; then 360 local valid suffix 361 valid=$( dot -Txxx 2>&1 ) 362 for suffix in ${valid##*:}; do 363 [ "$output" != "${output%.$suffix}" ] || continue 364 output_type=$suffix 365 break 366 done 367 fi 368 ngctl dot | dot ${output_type:+-T "$output_type"} -o "$output" 369} 370 371jng_show_usage="show" 372jng_show_descr="List possible NAME values for \`show NAME'" 373jng_show1_usage="show NAME" 374jng_show1_descr="Lists ng0_NAME [ng1_NAME ...]" 375jng_show2_usage="show [NAME]" 376jng_show() 377{ 378 local OPTIND=1 OPTARG flag 379 while getopts "" flag; do 380 case "$flag" in 381 *) action_usage show2 # NOTREACHED 382 esac 383 done 384 shift $(( $OPTIND - 1 )) 385 mustberoot_to_continue 386 if [ $# -eq 0 ]; then 387 ngctl ls | awk '$4=="bridge",$0=$2' | 388 xargs -rn1 -Ibridge ngctl show bridge: | 389 awk 'sub(/^ng[[:digit:]]+_/, "", $2), $0 = $2' | 390 sort -u 391 return 392 fi 393 ngctl ls | awk -v name="$1" ' 394 match($2, /^ng[[:digit:]]+_/) && 395 substr($2, RSTART + RLENGTH) == name && 396 $4 == "eiface", $0 = $2 397 ' | sort 398} 399 400jng_shutdown_usage="shutdown NAME" 401jng_shutdown_descr="Shutdown ng0_NAME [ng1_NAME ...]" 402jng_shutdown() 403{ 404 local OPTIND=1 OPTARG flag 405 while getopts "" flag; do 406 case "$flag" in 407 *) action_usage shutdown # NOTREACHED 408 esac 409 done 410 shift $(( $OPTIND -1 )) 411 local name="$1" 412 [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] || 413 action_usage shutdown # NOTREACHED 414 mustberoot_to_continue 415 jng_show "$name" | xargs -rn1 -I eiface ngctl shutdown eiface: 416} 417 418jng_stats_usage="stats NAME" 419jng_stats_descr="Show ng_bridge link statistics for NAME interfaces" 420jng_stats() 421{ 422 local OPTIND=1 OPTARG flag 423 while getopts "" flag; do 424 case "$flag" in 425 *) action_usage stats # NOTREACHED 426 esac 427 done 428 shift $(( $OPTIND -1 )) 429 local name="$1" 430 [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] || 431 action_usage stats # NOTREACHED 432 mustberoot_to_continue 433 for eiface in $( jng_show "$name" ); do 434 echo "$eiface:" 435 ngctl show $eiface: | awk ' 436 $3 == "bridge" && $5 ~ /^link/ { 437 bridge = $2 438 link = substr($5, 5) 439 system(sprintf("ngctl msg %s: getstats %u", 440 bridge, link)) 441 }' | fmt 2 | awk ' 442 /=/ && fl = index($0, "=") { 443 printf "%20s = %s\n", 444 substr($0, 0, fl-1), 445 substr($0, 0, fl+1) 446 } 447 ' # END-QUOTE 448 done 449} 450 451############################################################ MAIN 452 453# 454# Command-line arguments 455# 456action="$1" 457[ "$action" ] || usage # NOTREACHED 458 459# 460# Validate action argument 461# 462if [ "$BASH_VERSION" ]; then 463 type="$( type -t "jng_$action" )" || usage # NOTREACHED 464else 465 type="$( type "jng_$action" 2> /dev/null )" || usage # NOTREACHED 466fi 467case "$type" in 468*function) 469 shift 1 # action 470 eval "jng_$action" \"\$@\" 471 ;; 472*) usage # NOTREACHED 473esac 474 475################################################################################ 476# END 477################################################################################ 478