3ce83dbfcf
While "jng" or "jib" without arguments told you what each sub-command does, sub-command usage didn't tell you (e.g., "jng bridge" or "jib addm" gave only usage and not description).
419 lines
12 KiB
Bash
Executable File
419 lines
12 KiB
Bash
Executable File
#!/bin/sh
|
|
#-
|
|
# Copyright (c) 2016 Devin Teske
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions
|
|
# are met:
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
# SUCH DAMAGE.
|
|
#
|
|
# $FreeBSD$
|
|
#
|
|
############################################################ IDENT(1)
|
|
#
|
|
# $Title: if_bridge(4) management script for vnet jails $
|
|
#
|
|
############################################################ INFORMATION
|
|
#
|
|
# Use this tool with jail.conf(5) (or rc.conf(5) ``legacy'' configuration) to
|
|
# manage `vnet' interfaces for jails. Designed to automate the creation of vnet
|
|
# interface(s) during jail `prestart' and destroy said interface(s) during jail
|
|
# `poststop'.
|
|
#
|
|
# In jail.conf(5) format:
|
|
#
|
|
# ### BEGIN EXCERPT ###
|
|
#
|
|
# xxx {
|
|
# host.hostname = "xxx.yyy";
|
|
# path = "/vm/xxx";
|
|
#
|
|
# #
|
|
# # NB: Below 2-lines required
|
|
# # NB: The number of eNb_xxx interfaces should match the number of
|
|
# # arguments given to `jib addm xxx' in exec.prestart value.
|
|
# #
|
|
# vnet;
|
|
# vnet.interface = "e0b_xxx e1b_xxx ...";
|
|
#
|
|
# exec.clean;
|
|
# exec.system_user = "root";
|
|
# exec.jail_user = "root";
|
|
#
|
|
# #
|
|
# # NB: Below 2-lines required
|
|
# # NB: The number of arguments after `jib addm xxx' should match
|
|
# # the number of eNb_xxx arguments in vnet.interface value.
|
|
# #
|
|
# exec.prestart += "jib addm xxx em0 em1 ...";
|
|
# exec.poststop += "jib destroy xxx";
|
|
#
|
|
# # Standard recipe
|
|
# exec.start += "/bin/sh /etc/rc";
|
|
# exec.stop = "/bin/sh /etc/rc.shutdown";
|
|
# exec.consolelog = "/var/log/jail_xxx_console.log";
|
|
# mount.devfs;
|
|
#
|
|
# # Optional (default off)
|
|
# #allow.mount;
|
|
# #allow.set_hostname = 1;
|
|
# #allow.sysvipc = 1;
|
|
# #devfs_ruleset = "11"; # rule to unhide bpf for DHCP
|
|
# }
|
|
#
|
|
# ### END EXCERPT ###
|
|
#
|
|
# In rc.conf(5) ``legacy'' format (used when /etc/jail.conf does not exist):
|
|
#
|
|
# ### BEGIN EXCERPT ###
|
|
#
|
|
# jail_enable="YES"
|
|
# jail_list="xxx"
|
|
#
|
|
# #
|
|
# # Global presets for all jails
|
|
# #
|
|
# jail_devfs_enable="YES" # mount devfs
|
|
#
|
|
# #
|
|
# # Global options (default off)
|
|
# #
|
|
# #jail_mount_enable="YES" # mount /etc/fstab.{name}
|
|
# #jail_set_hostname_allow="YES" # Allow hostname to change
|
|
# #jail_sysvipc_allow="YES" # Allow SysV Interprocess Comm.
|
|
#
|
|
# # xxx
|
|
# jail_xxx_hostname="xxx.shxd.cx" # hostname
|
|
# jail_xxx_rootdir="/vm/xxx" # root directory
|
|
# jail_xxx_vnet_interfaces="e0b_xxx e1bxxx ..." # vnet interface(s)
|
|
# jail_xxx_exec_prestart0="jib addm xxx em0 em1 ..." # bridge interface(s)
|
|
# jail_xxx_exec_poststop0="jib destroy xxx" # destroy interface(s)
|
|
# #jail_xxx_mount_enable="YES" # mount /etc/fstab.xxx
|
|
# #jail_xxx_devfs_ruleset="11" # rule to unhide bpf for DHCP
|
|
#
|
|
# ### END EXCERPT ###
|
|
#
|
|
# Note that the legacy rc.conf(5) format is converted to
|
|
# /var/run/jail.{name}.conf by /etc/rc.d/jail if jail.conf(5) is missing.
|
|
#
|
|
# ASIDE: dhclient(8) inside a vnet jail...
|
|
#
|
|
# To allow dhclient(8) to work inside a vnet jail, make sure the following
|
|
# appears in /etc/devfs.rules (which should be created if it doesn't exist):
|
|
#
|
|
# [devfsrules_jail=11]
|
|
# add include $devfsrules_hide_all
|
|
# add include $devfsrules_unhide_basic
|
|
# add include $devfsrules_unhide_login
|
|
# add path 'bpf*' unhide
|
|
#
|
|
# And set ether devfs.ruleset="11" (jail.conf(5)) or
|
|
# jail_{name}_devfs_ruleset="11" (rc.conf(5)).
|
|
#
|
|
# NB: While this tool can't create every type of desirable topology, it should
|
|
# handle most setups, minus some which considered exotic or purpose-built.
|
|
#
|
|
############################################################ GLOBALS
|
|
|
|
pgm="${0##*/}" # Program basename
|
|
|
|
#
|
|
# Global exit status
|
|
#
|
|
SUCCESS=0
|
|
FAILURE=1
|
|
|
|
############################################################ FUNCTIONS
|
|
|
|
usage()
|
|
{
|
|
local action usage descr
|
|
exec >&2
|
|
echo "Usage: $pgm action [arguments]"
|
|
echo "Actions:"
|
|
for action in \
|
|
addm \
|
|
show \
|
|
show1 \
|
|
destroy \
|
|
; do
|
|
eval usage=\"\$jib_${action}_usage\"
|
|
[ "$usage" ] || continue
|
|
eval descr=\"\$jib_${action}_descr\"
|
|
printf "\t%s\n\t\t%s\n" "$usage" "$descr"
|
|
done
|
|
exit $FAILURE
|
|
}
|
|
|
|
action_usage()
|
|
{
|
|
local usage descr action="$1"
|
|
eval usage=\"\$jib_${action}_usage\"
|
|
echo "Usage: $pgm $usage" >&2
|
|
eval descr=\"\$jib_${action}_descr\"
|
|
printf "\t%s\n" "$descr"
|
|
exit $FAILURE
|
|
}
|
|
|
|
derive_mac()
|
|
{
|
|
local OPTIND=1 OPTARG __flag
|
|
local __mac_num= __make_pair=
|
|
while getopts 2n: __flag; do
|
|
case "$__flag" in
|
|
2) __make_pair=1 ;;
|
|
n) __mac_num=${OPTARG%%[^0-9]*} ;;
|
|
esac
|
|
done
|
|
shift $(( $OPTIND - 1 ))
|
|
|
|
if [ ! "$__mac_num" ]; then
|
|
eval __mac_num=\${_${iface}_num:--1}
|
|
__mac_num=$(( $__mac_num + 1 ))
|
|
eval _${iface}_num=\$__mac_num
|
|
fi
|
|
|
|
local __iface="$1" __name="$2" __var_to_set="$3" __var_to_set_b="$4"
|
|
local __iface_devid __new_devid __num __new_devid_b
|
|
#
|
|
# Calculate MAC address derived from given iface.
|
|
#
|
|
# The formula I'm using is ``NP:SS:SS:II:II:II'' where:
|
|
# + N denotes 4 bits used as a counter to support branching
|
|
# each parent interface up to 15 times under the same jail
|
|
# name (see S below).
|
|
# + P denotes the special nibble whose value, if one of
|
|
# 2, 6, A, or E (but usually 2) denotes a privately
|
|
# administered MAC address (while remaining routable).
|
|
# + S denotes 16 bits, the sum(1) value of the jail name.
|
|
# + I denotes bits that are inherited from parent interface.
|
|
#
|
|
# The S bits are a CRC-16 checksum of NAME, allowing the jail
|
|
# to change link numbers in ng_bridge(4) without affecting the
|
|
# MAC address. Meanwhile, if...
|
|
# + the jail NAME changes (e.g., it was duplicated and given
|
|
# a new name with no other changes)
|
|
# + the underlying network interface changes
|
|
# + the jail is moved to another host
|
|
# the MAC address will be recalculated to a new, similarly
|
|
# unique value preventing conflict.
|
|
#
|
|
__iface_devid=$( ifconfig $__iface ether | awk '/ether/,$0=$2' )
|
|
# ??:??:??:II:II:II
|
|
__new_devid=${__iface_devid#??:??:??} # => :II:II:II
|
|
# => :SS:SS:II:II:II
|
|
__num=$( set -- `echo -n "$__name" | sum` && echo $1 )
|
|
__new_devid=$( printf :%02x:%02x \
|
|
$(( $__num >> 8 & 255 )) $(( $__num & 255 )) )$__new_devid
|
|
# => P:SS:SS:II:II:II
|
|
case "$__iface_devid" in
|
|
?2:*) __new_devid=a$__new_devid __new_devid_b=e$__new_devid ;;
|
|
?[Ee]:*) __new_devid=2$__new_devid __new_devid_b=6$__new_devid ;;
|
|
*) __new_devid=2$__new_devid __new_devid_b=e$__new_devid
|
|
esac
|
|
# => NP:SS:SS:II:II:II
|
|
__new_devid=$( printf %x $(( $__mac_num & 15 )) )$__new_devid
|
|
__new_devid_b=$( printf %x $(( $__mac_num & 15 )) )$__new_devid_b
|
|
|
|
#
|
|
# Return derivative MAC address(es)
|
|
#
|
|
if [ "$__make_pair" ]; then
|
|
if [ "$__var_to_set" -a "$__var_to_set_b" ]; then
|
|
eval $__var_to_set=\$__new_devid
|
|
eval $__var_to_set_b=\$__new_devid_b
|
|
else
|
|
echo $__new_devid $__new_devid_b
|
|
fi
|
|
else
|
|
if [ "$__var_to_set" ]; then
|
|
eval $__var_to_set=\$__new_devid
|
|
else
|
|
echo $__new_devid
|
|
fi
|
|
fi
|
|
}
|
|
|
|
mustberoot_to_continue()
|
|
{
|
|
if [ "$( id -u )" -ne 0 ]; then
|
|
echo "Must run as root!" >&2
|
|
exit $FAILURE
|
|
fi
|
|
}
|
|
|
|
jib_addm_usage="addm [-b BRIDGE_NAME] NAME [!]iface0 [[!]iface1 ...]"
|
|
jib_addm_descr="Creates e0b_NAME [e1b_NAME ...]"
|
|
jib_addm()
|
|
{
|
|
local OPTIND=1 OPTARG flag bridge=bridge
|
|
while getopts b: flag; do
|
|
case "$flag" in
|
|
b) bridge="${OPTARG:-bridge}" ;;
|
|
*) action_usage addm # NOTREACHED
|
|
esac
|
|
done
|
|
shift $(( $OPTIND - 1 ))
|
|
|
|
local name="$1"
|
|
[ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -gt 1 ] ||
|
|
action_usage addm # NOTREACHED
|
|
shift 1 # name
|
|
|
|
mustberoot_to_continue
|
|
|
|
local iface eiface_devid_a eiface_devid_b
|
|
local new no_derive num quad i=0
|
|
for iface in $*; do
|
|
|
|
no_derive=
|
|
case "$iface" in
|
|
!*) iface=${iface#!} no_derive=1 ;;
|
|
esac
|
|
|
|
# Make sure the interface doesn't exist already
|
|
if ifconfig "e${i}a_$name" > /dev/null 2>&1; then
|
|
i=$(( $i + 1 ))
|
|
continue
|
|
fi
|
|
|
|
# Bring the interface up
|
|
ifconfig $iface up || return
|
|
|
|
# Make sure the interface has been bridged
|
|
if ! ifconfig "$iface$bridge" > /dev/null 2>&1; then
|
|
new=$( ifconfig bridge create ) || return
|
|
ifconfig $new addm $iface || return
|
|
ifconfig $new name "$iface$bridge" || return
|
|
ifconfig "$iface$bridge" up || return
|
|
fi
|
|
|
|
# Create a new interface to the bridge
|
|
new=$( ifconfig epair create ) || return
|
|
ifconfig "$iface$bridge" addm $new || return
|
|
|
|
# Rename the new interface
|
|
ifconfig $new name "e${i}a_$name" || return
|
|
ifconfig ${new%a}b name "e${i}b_$name" || return
|
|
ifconfig "e${i}a_$name" up || return
|
|
ifconfig "e${i}b_$name" up || return
|
|
|
|
#
|
|
# Set the MAC address of the new interface using a sensible
|
|
# algorithm to prevent conflicts on the network.
|
|
#
|
|
eiface_devid_a= eiface_devid_b=
|
|
[ "$no_derive" ] || derive_mac -2 $iface "$name" \
|
|
eiface_devid_a eiface_devid_b
|
|
if [ "$eiface_devid_a" -a "$eiface_devid_b" ]; then
|
|
ifconfig "e${i}a_$name" ether $eiface_devid_a
|
|
ifconfig "e${i}b_$name" ether $eiface_devid_b
|
|
fi > /dev/null 2>&1
|
|
|
|
i=$(( $i + 1 ))
|
|
done # for iface
|
|
}
|
|
|
|
jib_show_usage="show"
|
|
jib_show_descr="List possible NAME values for \`show NAME'"
|
|
jib_show1_usage="show NAME"
|
|
jib_show1_descr="Lists e0b_NAME [e1b_NAME ...]"
|
|
jib_show2_usage="show [NAME]"
|
|
jib_show()
|
|
{
|
|
local OPTIND=1 OPTARG flag
|
|
while getopts "" flag; do
|
|
case "$flag" in
|
|
*) action_usage show2 # NOTREACHED
|
|
esac
|
|
done
|
|
shift $(( $OPTIND - 1 ))
|
|
if [ $# -eq 0 ]; then
|
|
ifconfig | awk '
|
|
/^[^:[:space:]]+:/ {
|
|
iface = $1
|
|
sub(/:.*/, "", iface)
|
|
next
|
|
}
|
|
$1 == "groups:" {
|
|
for (n = split($0, group); n > 1; n--) {
|
|
if (group[n] != "bridge") continue
|
|
print iface
|
|
next
|
|
}
|
|
}' |
|
|
xargs -rn1 ifconfig |
|
|
awk '$1 == "member:" &&
|
|
sub(/^e[[:digit:]]+a_/, "", $2), $0 = $2' |
|
|
sort -u
|
|
return
|
|
fi
|
|
ifconfig | awk -v name="$1" '
|
|
match($0, /^e[[:digit:]]+a_/) && sub(/:.*/, "") &&
|
|
substr($1, RSTART + RLENGTH) == name
|
|
' | sort
|
|
}
|
|
|
|
jib_destroy_usage="destroy NAME"
|
|
jib_destroy_descr="Destroy e0b_NAME [e1b_NAME ...]"
|
|
jib_destroy()
|
|
{
|
|
local OPTIND=1 OPTARG flag
|
|
while getopts "" flag; do
|
|
case "$flag" in
|
|
*) action_usage destroy # NOTREACHED
|
|
esac
|
|
done
|
|
shift $(( $OPTIND -1 ))
|
|
local name="$1"
|
|
[ "${name:-x}" = "${name#*[!0-9a-zA-Z_]}" -a $# -eq 1 ] ||
|
|
action_usage destroy # NOTREACHED
|
|
mustberoot_to_continue
|
|
jib_show "$name" | xargs -rn1 -I eiface ifconfig eiface destroy
|
|
}
|
|
|
|
############################################################ MAIN
|
|
|
|
#
|
|
# Command-line arguments
|
|
#
|
|
action="$1"
|
|
[ "$action" ] || usage # NOTREACHED
|
|
|
|
#
|
|
# Validate action argument
|
|
#
|
|
if [ "$BASH_VERSION" ]; then
|
|
type="$( type -t "jib_$action" )" || usage # NOTREACHED
|
|
else
|
|
type="$( type "jib_$action" 2> /dev/null )" || usage # NOTREACHED
|
|
fi
|
|
case "$type" in
|
|
*function)
|
|
shift 1 # action
|
|
eval "jib_$action" \"\$@\"
|
|
;;
|
|
*) usage # NOTREACHED
|
|
esac
|
|
|
|
################################################################################
|
|
# END
|
|
################################################################################
|