Files
vm-bhyve/lib/vm-switch

429 lines
13 KiB
Bash

#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing 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 ``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 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.
# switch libraries
#
. "${LIB}/vm-switch-manual"
. "${LIB}/vm-switch-standard"
. "${LIB}/vm-switch-vale"
. "${LIB}/vm-switch-vxlan"
# create switches from rc list on init
# this should run once per boot to make sure switches from the
# configuration file have bridge interfaces. If any new switches are
# created, the create function takes care of setting them up
#
switch::init(){
local _switchlist _switch _type
config::core::get "_switchlist" "switch_list"
if [ -n "${_switchlist}" ]; then
for _switch in ${_switchlist}; do
# get the switch type
config::core::get "_type" "type_${_switch}"
case "${_type}" in
vxlan) switch::vxlan::init "${_switch}" ;;
manual) switch::manual::init "${_switch}" ;;
vale) ;;
*) switch::standard::init "${_switch}" ;;
esac
done
fi
}
# list switches configured
#
switch::list(){
local _switchlist _switch _type
local _id _format="%s^%s^%s^%s^%s^%s^%s^%s\n"
config::core::get "_switchlist" "switch_list"
{
printf "${_format}" "NAME" "TYPE" "IFACE" "ADDRESS" "PRIVATE" "MTU" "VLAN" "PORTS"
if [ -n "${_switchlist}" ]; then
for _switch in ${_switchlist}; do
# get the switch type
config::core::get "_type" "type_${_switch}"
case "${_type}" in
vale) switch::vale::show "${_switch}" "${_format}" ;;
vxlan) switch::vxlan::show "${_switch}" "${_format}" ;;
manual) switch::manual::show "${_switch}" "${_format}" ;;
*) switch::standard::show "${_switch}" "${_format}" ;;
esac
done
fi
} | column -ts^
}
# create a new virtual switch
#
# @param string _switch name of the switch to create
#
switch::create(){
local _switch
local _type="standard"
local _list _curr _vlan _if _bridge _addr _mtu _priv
# process options
while getopts t:i:n:b:a:m:p _opt; do
case ${_opt} in
t) _type="${OPTARG}" ;;
i) _if="${OPTARG}" ;;
n) _vlan="${OPTARG}" ;;
b) _bridge="${OPTARG}" ;;
a) _addr="${OPTARG}" ;;
m) _mtu="${OPTARG}" ;;
p) _priv="yes" ;;
*) util::usage ;;
esac
done
shift $((OPTIND - 1))
_switch="$1"
# check for a valid switch name
util::check_name "${_switch}" || util::err "invalid switch name - '${_switch}'"
# make sure it's not an existing name
config::core::get "_list" "switch_list"
for _curr in ${_list}; do
[ "${_switch}" = "${_curr}" ] && util::err "switch ${_switch} already exists"
done
# check vlan number
if [ -n "${_vlan}" ]; then
echo "${_vlan}" | egrep -qs '^[0-9]{1,4}$'
[ $? -eq 0 ] || util::err "invalid vlan number"
[ ${_vlan} -ge 4095 ] && util::err "invalid vlan number"
fi
# check address
if [ -n "${_addr}" ]; then
echo "${_addr}" | egrep -qs '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}$'
[ $? -eq 0 ] || util::err "address must be supplied in CIDR notation (a.b.c.d/prefix-len)"
fi
# check mtu
if [ -n "${_mtu}" ]; then
echo "${_mtu}" | egrep -qs '^[0-9]{3,4}$'
[ $? -eq 0 ] || util::err "invalid mtu"
[ ${_mtu} -gt 9000 ] && util::err "invalid mtu"
fi
# check switch type
case "${_type}" in
standard) switch::standard::create ;;
manual) switch::manual::create ;;
vale) switch::vale::create ;;
vxlan) switch::vxlan::create ;;
*) util::err "invalid switch type - '${_type}'" ;;
esac
}
# destroy a switch
# remove from configuration and unload any interfaces we created
#
# @param string _switch name of the switch to remove
#
switch::remove(){
local _switch="$1"
local _type
[ -z "${_switch}" ] && util::usage
# get the type of switch
config::core::get "_type" "type_${_switch}"
case "${_type}" in
standard) switch::standard::remove "${_switch}" ;;
manual) switch::manual::remove "${_switch}" ;;
vale) switch::vale::remove "${_switch}" ;;
vxlan) switch::vxlan::remove "${_switch}" ;;
*) util::err "unable to remove switch of unknown type" ;;
esac
# remove all configuration if there's no error
if [ $? -eq 0 ]; then
config::core::remove "switch_list" "${_switch}"
config::core::remove "ports_${_switch} vlan_${_switch} nat_${_switch} type_${_switch}"
config::core::remove "addr_${_switch} private_${_switch} mtu_${_switch}"
else
util::err "failed to remove virtual switch"
fi
# make sure the exit status indicates success,
# even if config::core::remove did not
return 0
}
# add a new interface to a switch
#
# @param string _switch name of the switch
# @param string _if the interface to add
#
switch::add_member(){
local _switch="$1"
local _if="$2"
local _type
[ -z "${_switch}" -o -z "${_if}" ] && util::usage
# get the type of switch
config::core::get "_type" "type_${_switch}"
case "${_type}" in
standard) switch::standard::add_member "${_switch}" "${_if}" ;;
manual) switch::manual::add_member "${_switch}" "${_if}" ;;
vale) switch::vale::add_member "${_switch}" "${_if}" ;;
vxlan) switch::vxlan::add_member "${_switch}" "${_if}" ;;
*) util::err "unable to configure switch of unknown type" ;;
esac
}
# remove a member interface from a virtual switch
#
# @param string _switch name of the switch
# @param string _if the interface to remove
#
switch::remove_member(){
local _switch="$1"
local _if="$2"
local _type
[ -z "${_switch}" -o -z "${_if}" ] && util::usage
# get the type of switch
config::core::get "_type" "type_${_switch}"
case "${_type}" in
standard) switch::standard::remove_member "${_switch}" "${_if}" ;;
manual) switch::manual::remove_member "${_switch}" "${_if}" ;;
vale) switch::vale::remove_member "${_switch}" "${_if}" ;;
vxlan) switch::vxlan::remove_member "${_switch}" "${_if}" ;;
*) util::err "unable to configure switch of unknown type" ;;
esac
}
# change the vlan number on a virtual switch
#
# @param string _switch name of the switch
# @param int _vlan the vlan number (0 to turn vlan off)
#
switch::vlan(){
local _switch="$1"
local _vlan="$2"
local _id _type
[ -z "${_switch}" -o -z "${_vlan}" ] && util::usage
switch::id "_id" "${_switch}"
switch::type "_type" "${_switch}"
[ -z "${_id}" ] && util::err "unable to locate specified virtual switch"
echo "${_vlan}" | egrep -qs '^[0-9]{1,4}$'
[ $? -eq 0 ] || util::err "invalid vlan number"
[ ${_vlan} -ge 4095 ] && util::err "invalid vlan number"
case "${_type}" in
standard) switch::standard::vlan "${_switch}" "${_vlan}" ;;
manual) switch::manual::vlan "${_switch}" "${_vlan}" ;;
vale) switch::vale::vlan "${_switch}" "${_vlan}" ;;
vxlan) switch::vxlan::vlan "${_switch}" "${_vlan}" ;;
*) util::err "unable to configure switch of unknown type" ;;
esac
}
# enable or diable private flag on a switch
# note that we don't update existing interfaces; this
# makes things easy for us and any guests booted after
# will get the new setting
#
# @param string _switch the switch to update
# @param string _priv on,yes|off,no
#
switch::private(){
local _switch="$1"
local _priv="$2"
local _type
# try to get switch type
[ -z "${_switch}" -o -z "${_priv}" ] && util::usage
switch::type "_type" "${_switch}" || util::err "specified switch does not appear to be valid"
case "${_type}" in
standard|manual|vxlan)
if util::yesno "${_priv}"; then
config::core::set "private_${_switch}" "yes"
else
config::core::set "private_${_switch}" "no"
fi
;;
vale)
util::err "unable to configure private mode on vale switches"
;;
*)
util::err "unable to configure switch of unknown type"
;;
esac
}
# enable or disable nat functionality on a virtual switch
#
# @param string _switch name of the switch
# @param string _nat on|off
#
switch::nat(){
util::warn "internal nat support is currently disabled"
util::warn "please add an address to the virtual switch and configure your firewall for NAT manually"
}
# set or remove ip address from a virtual switch
#
# @param string _switch name of the switch
# @param string _addr the ip address to add (or "none" to remove
#
switch::address(){
local _switch="$1"
local _addr="$2"
local _id _type
[ -z "${_switch}" ] && util::usage
switch::id "_id" "${_switch}"
switch::type "_type" "${_switch}"
[ -z "${_id}" ] && util::err "unable to locate specified virtual switch"
# check address
if [ -n "${_addr}" ] && [ "${_addr}" != "none" ]; then
echo "${_addr}" | egrep -qs '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}$'
[ $? -eq 0 ] || util::err "address must be supplied in CIDR notation (a.b.c.d/prefix-len)"
fi
case "${_type}" in
standard) switch::standard::address "${_switch}" "${_addr}" ;;
manual) ;&
vale) ;&
vxlan) util::err "feature not currently supported on switches of this type" ;;
*) util::err "unable to configure switch of unknown type" ;;
esac
}
# return the type for a switch
#
# @param string _var variable to put type into
# @param string _switch the switch name
#
switch::type(){
local _var="$1"
local _switch="$2"
[ -z "${_switch}" ] && return 1
config::core::get "${_var}" "type_${_switch}" "standard"
}
# check if a switch is configured for private members
#
# @param string _switch switch name
# @return succes (0) if it's private
#
switch::is_private(){
local _switch="$1"
local _priv
config::core::get "_priv" "private_${_switch}"
util::yesno "${_priv}"
}
# get the bridge id for a virtual switch
#
# @param string _var variable to put name into
# @param string _switch the name of the switch
#
switch::id(){
local _var="$1"
local _switch="$2"
local _type
[ -z "${_switch}" ] && return 1
# get switch type
config::core::get "_type" "type_${_switch}"
case "${_type}" in
vale) switch::vale::id "${_var}" "${_switch}" ;;
manual) switch::manual::id "${_var}" "${_switch}" ;;
*) switch::standard::id "${_var}" "${_switch}" ;;
esac
}
# get a virt interface id for a port/switch
#
# @param string _var variable name to put result into
# @param string _switch switch name to get id for
#
switch::__viid(){
local _hash=$(md5 -qs "${2}" | cut -c1-5)
setvar "$1" "viid-${_hash}@"
}
# retrieve interface name, given a switch name
# we convert to viid then look for the matching group
#
# @param string _var variable to put interface name into
# @param string _switch the switch name
#
switch::find(){
local _var="$1"
local _switch="$2"
local _viid _name
switch::__viid "_viid" "${_switch}"
_name=$(ifconfig -g "${_viid}" 2>/dev/null)
[ -n "${_name}" ] && setvar "${_var}" "${_name}"
}
# mark an interface with a unique viid
# i say unique, its 5 chars from an md5 hash which
# should be enough for half a dozen switches
#
# @parem string _switch switch name
# @param string _iface interface to mark
#
switch::set_viid(){
local _switch="$1"
local _iface="$2"
local _viid
switch::__viid "_viid" "${_switch}"
ifconfig "${_iface}" group "${_viid}" >/dev/null 2>&1
}