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$ 28# 29############################################################ IDENT(1) 30# 31# $Title: if_bridge(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. In jail.conf(5) format: 37# 38# ### BEGIN EXCERPT ### 39# 40# xxx { 41# host.hostname = "xxx.yyy"; 42# path = "/vm/xxx"; 43# 44# # 45# # NB: Below 2-lines required 46# # NB: The number of eNb_xxx interfaces should match the number of 47# # arguments given to `jib addm xxx' in exec.prestart value. 48# # 49# vnet; 50# vnet.interface = "e0b_xxx e1b_xxx ..."; 51# 52# exec.clean; 53# exec.system_user = "root"; 54# exec.jail_user = "root"; 55# 56# # 57# # NB: Below 2-lines required 58# # NB: The number of arguments after `jib addm xxx' should match 59# # the number of eNb_xxx arguments in vnet.interface value. 60# # 61# exec.prestart += "jib addm xxx em0 em1 ..."; 62# exec.poststop += "jib destroy xxx"; 63# 64# # Standard recipe 65# exec.start += "/bin/sh /etc/rc"; 66# exec.stop = "/bin/sh /etc/rc.shutdown"; 67# exec.consolelog = "/var/log/jail_xxx_console.log"; 68# mount.devfs; 69# 70# # Optional (default off) 71# #allow.mount; 72# #allow.set_hostname = 1; 73# #allow.sysvipc = 1; 74# #devfs_ruleset = "11"; # rule to unhide bpf for DHCP 75# } 76# 77# ### END EXCERPT ### 78# 79# In rc.conf(5) ``legacy'' format (used when /etc/jail.conf does not exist): 80# 81# ### BEGIN EXCERPT ### 82# 83# jail_enable="YES" 84# jail_list="xxx" 85# 86# # 87# # Global presets for all jails 88# # 89# jail_devfs_enable="YES" # mount devfs 90# 91# # 92# # Global options (default off) 93# # 94# #jail_mount_enable="YES" # mount /etc/fstab.{name} 95# #jail_set_hostname_allow="YES" # Allow hostname to change 96# #jail_sysvipc_allow="YES" # Allow SysV Interprocess Comm. 97# 98# # xxx 99# jail_xxx_hostname="xxx.shxd.cx" # hostname 100# jail_xxx_rootdir="/vm/xxx" # root directory 101# jail_xxx_vnet_interfaces="e0b_xxx e1bxxx ..." # vnet interface(s) 102# jail_xxx_exec_prestart0="jib addm xxx em0 em1 ..." # bridge interface(s) 103# jail_xxx_exec_poststop0="jib destroy xxx" # destroy interface(s) 104# #jail_xxx_mount_enable="YES" # mount /etc/fstab.xxx 105# #jail_xxx_devfs_ruleset="11" # rule to unhide bpf for DHCP 106# 107# ### END EXCERPT ### 108# 109# Note that the legacy rc.conf(5) format is converted to 110# /var/run/jail.{name}.conf by /etc/rc.d/jail if jail.conf(5) is missing. 111# 112# ASIDE: dhclient(8) inside a vnet jail... 113# 114# To allow dhclient(8) to work inside a vnet jail, make sure the following 115# appears in /etc/devfs.rules (which should be created if it doesn't exist): 116# 117# [devfsrules_jail=11] 118# add include $devfsrules_hide_all 119# add include $devfsrules_unhide_basic 120# add include $devfsrules_unhide_login 121# add include $devfsrules_unhide_bpf 122# 123# And set ether devfs.ruleset="11" (jail.conf(5)) or 124# jail_{name}_devfs_ruleset="11" (rc.conf(5)). 125# 126# NB: While this tool can't create every type of desirable topology, it should 127# handle most setups, minus some which considered exotic or purpose-built. 128# 129############################################################ GLOBALS 130 131pgm="${0##*/}" # Program basename 132 133# 134# Global exit status 135# 136SUCCESS=0 137FAILURE=1 138 139############################################################ FUNCTIONS 140 141usage() 142{ 143 local action usage descr 144 exec >&2 145 echo "Usage: $pgm action [arguments]" 146 echo "Actions:" 147 for action in \ 148 addm \ 149 show \ 150 show1 \ 151 destroy \ 152 ; do 153 eval usage=\"\$jib_${action}_usage\" 154 [ "$usage" ] || continue 155 eval descr=\"\$jib_${action}_descr\" 156 printf "\t%s\n\t\t%s\n" "$usage" "$descr" 157 done 158 exit $FAILURE 159} 160 161action_usage() 162{ 163 local usage action="$1" 164 eval usage=\"\$jib_${action}_usage\" 165 echo "Usage: $pgm $usage" >&2 166 exit $FAILURE 167} 168 169mustberoot_to_continue() 170{ 171 if [ "$( id -u )" -ne 0 ]; then 172 echo "Must run as root!" >&2 173 exit $FAILURE 174 fi 175} 176 177jib_addm_usage="addm [-b BRIDGE_NAME] NAME interface0 [interface1 ...]" 178jib_addm_descr="Creates e0b_NAME [e1b_NAME ...]" 179jib_addm() 180{ 181 local OPTIND=1 OPTARG flag bridge=bridge 182 while getopts b: flag; do 183 case "$flag" in 184 b) bridge="${OPTARG:-bridge}" ;; 185 *) action_usage addm # NOTREACHED 186 esac 187 done 188 shift $(( $OPTIND - 1 )) 189 190 local name="$1" 191 [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -gt 1 ] || 192 action_usage addm # NOTREACHED 193 shift 1 # name 194 195 mustberoot_to_continue 196 197 local iface iface_devid eiface_devid_a eiface_devid_b 198 local new num quad i=0 199 for iface in $*; do 200 201 # 1. Make sure the interface doesn't exist already 202 ifconfig "e${i}a_$name" > /dev/null 2>&1 && continue 203 204 # 2. Bring the interface up 205 ifconfig $iface up || return 206 207 # 3. Make sure the interface has been bridged 208 if ! ifconfig "$iface$bridge" > /dev/null 2>&1; then 209 new=$( ifconfig bridge create ) || return 210 ifconfig $new addm $iface || return 211 ifconfig $new name "$iface$bridge" || return 212 fi 213 214 # 4. Create a new interface to the bridge 215 new=$( ifconfig epair create ) || return 216 ifconfig "$iface$bridge" addm $new || return 217 218 # 5. Rename the new interface 219 ifconfig $new name "e${i}a_$name" || return 220 ifconfig ${new%a}b name "e${i}b_$name" || return 221 222 # 223 # 6. Set the MAC address of the new interface using a sensible 224 # algorithm to prevent conflicts on the network. 225 # 226 # The formula I'm using is ``SP:SS:SI:II:II:II'' where: 227 # + S denotes 16 bits of sum(1) data, split because P (below). 228 # + P denotes the special nibble whose value, if one of 229 # 2, 6, A, or E (but usually 2) denotes a privately 230 # administered MAC address (while remaining routable). 231 # + I denotes bits that are inherited from parent interface. 232 # 233 # The S bits are a CRC-16 checksum of NAME, allowing the jail 234 # to change the epair(4) generation order without affecting the 235 # MAC address. Meanwhile, if the jail NAME changes (e.g., it 236 # was duplicated and given a new name with no other changes), 237 # the underlying network interface changes, or the jail is 238 # moved to another host, the MAC address will be recalculated 239 # to a new, similarly unique value preventing conflict. 240 # 241 iface_devid=$( ifconfig $iface ether | awk '/ether/,$0=$2' ) 242 eiface_devid_a=${iface_devid#??:??:?} 243 eiface_devid_b=${iface_devid#??:??:?} 244 num=$( set -- `echo -n $name | sum` && echo $1 ) 245 quad=$(( $num & 15 )) 246 case "$quad" in 247 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 248 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 249 esac 250 eiface_devid_a=:$quad$eiface_devid_a 251 eiface_devid_b=:$quad$eiface_devid_b 252 num=$(( $num >> 4 )) 253 quad=$(( $num & 15 )) 254 case "$quad" in 255 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 256 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 257 esac 258 eiface_devid_a=$quad$eiface_devid_a 259 eiface_devid_b=$quad$eiface_devid_b 260 num=$(( $num >> 4 )) 261 quad=$(( $num & 15 )) 262 case "$quad" in 263 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 264 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 265 esac 266 eiface_devid_a=2:$quad$eiface_devid_a 267 eiface_devid_b=6:$quad$eiface_devid_b 268 num=$(( $num >> 4 )) 269 quad=$(( $num & 15 )) 270 case "$quad" in 271 10) quad=a ;; 11) quad=b ;; 12) quad=c ;; 272 13) quad=d ;; 14) quad=e ;; 15) quad=f ;; 273 esac 274 eiface_devid_a=$quad$eiface_devid_a 275 eiface_devid_b=$quad$eiface_devid_b 276 ifconfig "e${i}a_$name" ether $eiface_devid_a > /dev/null 2>&1 277 ifconfig "e${i}b_$name" ether $eiface_devid_b > /dev/null 2>&1 278 279 i=$(( $i + 1 )) # on to next ng{i}_name 280 done # for iface 281} 282 283jib_show_usage="show" 284jib_show_descr="List possible NAME values for \`show NAME'" 285jib_show1_usage="show NAME" 286jib_show1_descr="Lists ng0_NAME [ng1_NAME ...]" 287jib_show2_usage="show [NAME]" 288jib_show() 289{ 290 local OPTIND=1 OPTARG flag 291 while getopts "" flag; do 292 case "$flag" in 293 *) action_usage show2 # NOTREACHED 294 esac 295 done 296 shift $(( $OPTIND - 1 )) 297 if [ $# -eq 0 ]; then 298 ifconfig | awk ' 299 /^[^:[:space:]]+:/ { 300 iface = $1 301 sub(/:.*/, "", iface) 302 next 303 } 304 $1 == "groups:" { 305 for (n = split($0, group); n > 1; n--) { 306 if (group[n] != "bridge") continue 307 print iface 308 next 309 } 310 }' | 311 xargs -rn1 ifconfig | 312 awk '$1 == "member:" && 313 sub(/^e[[:digit:]]+a_/, "", $2), $0 = $2' | 314 sort -u 315 return 316 fi 317 ifconfig | awk -v name="$1" ' 318 match($0, /^e[[:digit:]]+a_/) && sub(/:.*/, "") && 319 substr($1, RSTART + RLENGTH) == name 320 ' | sort 321} 322 323jib_destroy_usage="destroy NAME" 324jib_destroy_descr="Destroy e0b_NAME [e1b_NAME ...]" 325jib_destroy() 326{ 327 local OPTIND=1 OPTARG flag 328 while getopts "" flag; do 329 case "$flag" in 330 *) action_usage destroy # NOTREACHED 331 esac 332 done 333 shift $(( $OPTIND -1 )) 334 local name="$1" 335 [ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] || 336 action_usage destroy # NOTREACHED 337 mustberoot_to_continue 338 jib_show "$name" | xargs -rn1 -I eiface ifconfig eiface destroy 339} 340 341############################################################ MAIN 342 343# 344# Command-line arguments 345# 346action="$1" 347[ "$action" ] || usage # NOTREACHED 348 349# 350# Validate action argument 351# 352if [ "$BASH_VERSION" ]; then 353 type="$( type -t "jib_$action" )" || usage # NOTREACHED 354else 355 type="$( type "jib_$action" 2> /dev/null )" || usage # NOTREACHED 356fi 357case "$type" in 358*function) 359 shift 1 # action 360 eval "jib_$action" \"\$@\" 361 ;; 362*) usage # NOTREACHED 363esac 364 365################################################################################ 366# END 367################################################################################ 368