1#!/bin/sh 2############################################################ LICENSE 3# 4# SPDX-License-Identifier: BSD-2-Clause 5# 6# Copyright (c) 2016-2026 Devin Teske <dteske@FreeBSD.org> 7# 8############################################################ IDENT(1) 9# 10# $Title: if_bridge(4) management script for vnet jails $ 11# $Version: 9.2 $ 12# 13############################################################ INFORMATION 14# 15# Use this tool with jail.conf(5) (or rc.conf(5) ``legacy'' configuration) to 16# manage `vnet' interfaces for jails. Designed to automate the creation of vnet 17# interface(s) during jail `prestart' and destroy said interface(s) during jail 18# `poststop'. 19# 20# In jail.conf(5) format: 21# 22# ### BEGIN EXCERPT ### 23# 24# xxx { 25# host.hostname = "xxx.yyy"; 26# path = "/vm/xxx"; 27# 28# # 29# # NB: Below 2-lines required 30# # NB: The number of eNb_xxx interfaces should match the number of 31# # arguments given to `jib addm xxx' in exec.prestart value. 32# # 33# vnet; 34# vnet.interface = e0b_xxx, e1b_xxx, ...; 35# 36# exec.clean; 37# exec.system_user = "root"; 38# exec.jail_user = "root"; 39# 40# # 41# # NB: Below 2-lines required 42# # NB: The number of arguments after `jib addm xxx' should match 43# # the number of eNb_xxx arguments in vnet.interface value. 44# # 45# exec.prestart += "jib addm xxx em0 em1 ..."; 46# exec.poststop += "jib destroy xxx"; 47# 48# # Standard recipe 49# exec.start += "/bin/sh /etc/rc"; 50# exec.stop = "/bin/sh /etc/rc.shutdown jail"; 51# exec.consolelog = "/var/log/jail_xxx_console.log"; 52# mount.devfs; 53# 54# # Optional (default off) 55# #allow.mount; 56# #allow.set_hostname = 1; 57# #allow.sysvipc = 1; 58# #devfs_ruleset = "11"; # rule to unhide bpf for DHCP 59# } 60# 61# ### END EXCERPT ### 62# 63# In rc.conf(5) ``legacy'' format (used when /etc/jail.conf does not exist): 64# 65# ### BEGIN EXCERPT ### 66# 67# jail_enable="YES" 68# jail_list="xxx" 69# 70# # 71# # Global presets for all jails 72# # 73# jail_devfs_enable="YES" # mount devfs 74# 75# # 76# # Global options (default off) 77# # 78# #jail_mount_enable="YES" # mount /etc/fstab.{name} 79# #jail_set_hostname_allow="YES" # Allow hostname to change 80# #jail_sysvipc_allow="YES" # Allow SysV Interprocess Comm. 81# 82# # xxx 83# jail_xxx_hostname="xxx.shxd.cx" # hostname 84# jail_xxx_rootdir="/vm/xxx" # root directory 85# jail_xxx_vnet_interfaces="e0b_xxx e1bxxx ..." # vnet interface(s) 86# jail_xxx_exec_prestart0="jib addm xxx em0 em1 ..." # bridge interface(s) 87# jail_xxx_exec_poststop0="jib destroy xxx" # destroy interface(s) 88# #jail_xxx_mount_enable="YES" # mount /etc/fstab.xxx 89# #jail_xxx_devfs_ruleset="11" # rule to unhide bpf for DHCP 90# 91# ### END EXCERPT ### 92# 93# Note that the legacy rc.conf(5) format is converted to 94# /var/run/jail.{name}.conf by /etc/rc.d/jail if jail.conf(5) is missing. 95# 96# ASIDE: dhclient(8) inside a vnet jail... 97# 98# To allow dhclient(8) to work inside a vnet jail, make sure the following 99# appears in /etc/devfs.rules (which should be created if it doesn't exist): 100# 101# [devfsrules_jail=11] 102# add include $devfsrules_hide_all 103# add include $devfsrules_unhide_basic 104# add include $devfsrules_unhide_login 105# add path 'bpf*' unhide 106# 107# And set ether devfs.ruleset="11" (jail.conf(5)) or 108# jail_{name}_devfs_ruleset="11" (rc.conf(5)). 109# 110# NB: While this tool can't create every type of desirable topology, it should 111# handle most setups, minus some which considered exotic or purpose-built. 112# 113############################################################ GLOBALS 114 115VERSION='$Version: 9.2 $' 116 117pgm="${0##*/}" # Program basename 118 119# 120# Global exit status 121# 122SUCCESS=0 123FAILURE=1 124 125############################################################ FUNCTIONS 126 127usage() 128{ 129 local optfmt="\t%-5s %s\n" 130 local action usage descr 131 exec >&2 132 printf "Usage: %s [-hv] action [arguments]\n" "$pgm" 133 printf "Options:\n" 134 printf "$optfmt" "-h" "Print this usage statement and exit." 135 printf "$optfmt" "-v" "Print version information and exit." 136 printf "Actions:\n" 137 for action in \ 138 addm \ 139 show \ 140 show1 \ 141 destroy \ 142 ; do 143 eval usage=\"\$jib_${action}_usage\" 144 [ "$usage" ] || continue 145 eval descr=\"\$jib_${action}_descr\" 146 printf "\t%s\n\t\t%s\n" "$usage" "$descr" 147 done 148 exit $FAILURE 149} 150 151action_usage() 152{ 153 local usage descr action="$1" 154 eval usage=\"\$jib_${action}_usage\" 155 echo "Usage: $pgm $usage" >&2 156 eval descr=\"\$jib_${action}_descr\" 157 printf "\t%s\n" "$descr" 158 exit $FAILURE 159} 160 161iface_encode() 162{ 163 LC_ALL=C iface="$1" awk 'BEGIN { 164 for (n = 0; n < 256; n++) 165 pack[sprintf("%c", n)] = sprintf("_%02x", n) 166 numbers = "0123456789" 167 uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 168 lowercase = "abcdefghijklmnopqrstuvwxyz" 169 valid = "[" numbers uppercase lowercase "]" 170 iface = ENVIRON["iface"] 171 len = length(iface) 172 for (n = 1; n <= len; n++) { 173 let = substr(iface, n, 1) 174 _iface = _iface (let ~ valid ? let : pack[let]) 175 } 176 print _iface 177 }' 178} 179 180derive_mac() 181{ 182 local OPTIND=1 OPTARG __flag 183 local __mac_num= __make_pair= 184 while getopts 2n: __flag; do 185 case "$__flag" in 186 2) __make_pair=1 ;; 187 n) __mac_num=${OPTARG%%[^0-9]*} ;; 188 esac 189 done 190 shift $(( $OPTIND - 1 )) 191 192 local __iface="$1" 193 if [ ! "$__mac_num" ]; then 194 local __iface_encoded 195 __iface_encoded=$( iface_encode "$__iface" ) 196 eval __mac_num=\${_${__iface_encoded}_num:--1} 197 __mac_num=$(( $__mac_num + 1 )) 198 eval _${__iface_encoded}_num=\$__mac_num 199 fi 200 201 local __name="$2" __var_to_set="$3" __var_to_set_b="$4" 202 local __iface_devid __new_devid __num __new_devid_b 203 # 204 # Calculate MAC address derived from given iface. 205 # 206 # The formula I'm using is ``NP:SS:SS:II:II:II'' where: 207 # + N denotes 4 bits used as a counter to support branching 208 # each parent interface up to 15 times under the same jail 209 # name (see S below). 210 # + P denotes the special nibble whose value, if one of 211 # 2, 6, A, or E (but usually 2) denotes a privately 212 # administered MAC address (while remaining routable). 213 # + S denotes 16 bits, the sum(1) value of the jail name. 214 # + I denotes bits that are inherited from parent interface. 215 # 216 # The S bits are a CRC-16 checksum of NAME, allowing the jail 217 # to change link numbers in ng_bridge(4) without affecting the 218 # MAC address. Meanwhile, if... 219 # + the jail NAME changes (e.g., it was duplicated and given 220 # a new name with no other changes) 221 # + the underlying network interface changes 222 # + the jail is moved to another host 223 # the MAC address will be recalculated to a new, similarly 224 # unique value preventing conflict. 225 # 226 __iface_devid=$( ifconfig $__iface ether | awk '/ether/,$0=$2' ) 227 # ??:??:??:II:II:II 228 __new_devid=${__iface_devid#??:??:??} # => :II:II:II 229 # => :SS:SS:II:II:II 230 __num=$( set -- `echo -n "$__name" | sum` && echo $1 ) 231 __new_devid=$( printf :%02x:%02x \ 232 $(( $__num >> 8 & 255 )) $(( $__num & 255 )) )$__new_devid 233 # => P:SS:SS:II:II:II 234 case "$__iface_devid" in 235 ?2:*) __new_devid=a$__new_devid __new_devid_b=e$__new_devid ;; 236 ?[Ee]:*) __new_devid=2$__new_devid __new_devid_b=6$__new_devid ;; 237 *) __new_devid=2$__new_devid __new_devid_b=e$__new_devid 238 esac 239 # => NP:SS:SS:II:II:II 240 __new_devid=$( printf %x $(( $__mac_num & 15 )) )$__new_devid 241 __new_devid_b=$( printf %x $(( $__mac_num & 15 )) )$__new_devid_b 242 243 # 244 # Return derivative MAC address(es) 245 # 246 if [ "$__make_pair" ]; then 247 if [ "$__var_to_set" -a "$__var_to_set_b" ]; then 248 eval $__var_to_set=\$__new_devid 249 eval $__var_to_set_b=\$__new_devid_b 250 else 251 echo $__new_devid $__new_devid_b 252 fi 253 else 254 if [ "$__var_to_set" ]; then 255 eval $__var_to_set=\$__new_devid 256 else 257 echo $__new_devid 258 fi 259 fi 260} 261 262mustberoot_to_continue() 263{ 264 if [ "$( id -u )" -ne 0 ]; then 265 echo "Must run as root!" >&2 266 exit $FAILURE 267 fi 268} 269 270jib_addm_usage="addm [-b BRIDGE_NAME] NAME [!]iface0 [[!]iface1 ...]" 271jib_addm_descr="Creates e0b_NAME [e1b_NAME ...]" 272jib_addm() 273{ 274 local OPTIND=1 OPTARG flag bridge=bridge 275 while getopts b: flag; do 276 case "$flag" in 277 b) bridge="${OPTARG:-bridge}" ;; 278 *) action_usage addm # NOTREACHED 279 esac 280 done 281 shift $(( $OPTIND - 1 )) 282 283 local name="$1" 284 [ "${name:-x}" = "${name#*[![:print:]]}" -a $# -gt 1 ] || 285 action_usage addm # NOTREACHED 286 shift 1 # name 287 288 mustberoot_to_continue 289 290 local iface eiface_devid_a eiface_devid_b 291 local new no_derive num quad i=0 292 for iface in $*; do 293 294 no_derive= 295 case "$iface" in 296 !*) iface=${iface#!} no_derive=1 ;; 297 esac 298 299 # Make sure the interface doesn't exist already 300 if ifconfig "e${i}a_$name" > /dev/null 2>&1; then 301 i=$(( $i + 1 )) 302 continue 303 fi 304 305 # Bring the interface up 306 ifconfig $iface up || return 307 308 # Make sure the interface has been bridged 309 if ! ifconfig "$iface$bridge" > /dev/null 2>&1; then 310 new=$( ifconfig bridge create ) || return 311 ifconfig $new addm $iface || return 312 ifconfig $new name "$iface$bridge" || return 313 ifconfig "$iface$bridge" up || return 314 fi 315 316 # Create a new interface to the bridge 317 new=$( ifconfig epair create ) || return 318 ifconfig "$iface$bridge" addm $new || return 319 320 # Rename the new interface 321 ifconfig $new name "e${i}a_$name" || return 322 ifconfig ${new%a}b name "e${i}b_$name" || return 323 ifconfig "e${i}a_$name" up || return 324 ifconfig "e${i}b_$name" up || return 325 326 # 327 # Set the MAC address of the new interface using a sensible 328 # algorithm to prevent conflicts on the network. 329 # 330 eiface_devid_a= eiface_devid_b= 331 [ "$no_derive" ] || derive_mac -2 $iface "$name" \ 332 eiface_devid_a eiface_devid_b 333 if [ "$eiface_devid_a" -a "$eiface_devid_b" ]; then 334 ifconfig "e${i}a_$name" ether $eiface_devid_a 335 ifconfig "e${i}b_$name" ether $eiface_devid_b 336 fi > /dev/null 2>&1 337 338 i=$(( $i + 1 )) 339 done # for iface 340} 341 342jib_show_usage="show" 343jib_show_descr="List possible NAME values for \`show NAME'" 344jib_show1_usage="show NAME" 345jib_show1_descr="Lists e0b_NAME [e1b_NAME ...]" 346jib_show2_usage="show [NAME]" 347jib_show() 348{ 349 local OPTIND=1 OPTARG flag 350 while getopts "" flag; do 351 case "$flag" in 352 *) action_usage show2 # NOTREACHED 353 esac 354 done 355 shift $(( $OPTIND - 1 )) 356 if [ $# -eq 0 ]; then 357 ifconfig | awk ' 358 /^[^:[:space:]]+:/ { 359 iface = $1 360 sub(/:.*/, "", iface) 361 next 362 } 363 $1 == "groups:" { 364 for (n = split($0, group); n > 1; n--) { 365 if (group[n] != "bridge") continue 366 print iface 367 next 368 } 369 }' | 370 xargs -rn1 ifconfig | 371 awk '$1 == "member:" && 372 sub(/^e[[:digit:]]+a_/, "", $2), $0 = $2' | 373 sort -u 374 return 375 fi 376 ifconfig | awk -v name="$1" ' 377 match($0, /^e[[:digit:]]+a_/) && sub(/:.*/, "") && 378 substr($1, RSTART + RLENGTH) == name 379 ' | sort 380} 381 382jib_destroy_usage="destroy NAME" 383jib_destroy_descr="Destroy e0b_NAME [e1b_NAME ...]" 384jib_destroy() 385{ 386 local OPTIND=1 OPTARG flag 387 while getopts "" flag; do 388 case "$flag" in 389 *) action_usage destroy # NOTREACHED 390 esac 391 done 392 shift $(( $OPTIND -1 )) 393 local name="$1" 394 [ "${name:-x}" = "${name#*[![:print:]]}" -a $# -eq 1 ] || 395 action_usage destroy # NOTREACHED 396 mustberoot_to_continue 397 jib_show "$name" | xargs -rn1 -I eiface ifconfig eiface destroy 398} 399 400############################################################ MAIN 401 402# 403# Command-line arguments 404# 405action="$1" 406[ "$action" ] || usage # NOTREACHED 407 408# 409# Validate action argument 410# 411case "$action" in 412-h) usage ;; # NOTREACHED 413-v) VERSION="${VERSION#*: }" 414 echo "${VERSION% $}" 415 exit $SUCCESS ;; 416-*) usage ;; # NOTREACHED 417esac 418if [ "$BASH_VERSION" ]; then 419 type="$( type -t "jib_$action" )" || usage # NOTREACHED 420else 421 type="$( type "jib_$action" 2> /dev/null )" || usage # NOTREACHED 422fi 423case "$type" in 424*function) 425 shift 1 # action 426 eval "jib_$action" \"\$@\" 427 ;; 428*) usage # NOTREACHED 429esac 430 431################################################################################ 432# END 433################################################################################ 434