Files
vm-bhyve/lib/vm-switch

603 lines
20 KiB
Plaintext
Raw Normal View History

#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2015 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.
# 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 bridge is done at the same time
#
__switch_init(){
local _switchlist _switch _id
local _nat _havenat=0 _bridge _vale
__config_load "${vm_dir}/.config/switch"
__config_get "_switchlist" "switch_list"
# create bridges for each switch if they don't already exist
if [ -n "${_switchlist}" ]; then
for _switch in ${_switchlist}; do
__switch_get_ident "_id" "${_switch}"
[ -n "${_id}" ] && continue
__config_get "_nat" "nat_${_switch}"
__config_get "_bridge" "bridge_${_switch}"
# do nothing if it's a vale switch
__config_get "_vale" "vale_${_switch}"
__checkyesno "${_vale}" && continue
if [ -n "${_bridge}" ]; then
ifconfig "${_bridge}" description "vm-${_switch}" up >/dev/null 2>&1
else
_id=$(ifconfig bridge create)
[ $? -ne 0 ] && __err "failed to create bridge interface"
ifconfig "${_id}" description "vm-${_switch}" up
# add all member interfaces
__switch_add_allmembers "${_switch}" "${_id}"
fi
[ -n "${_nat}" ] &&_havenat="1"
done
fi
# only load dnsmasq/pf if we have a nat switch
[ "${_havenat}" = "1" ] && __switch_nat_init
}
# list switches, currently just from stored configuration
#
__switch_list(){
local _switchlist _portlist _switch _port
local _vlan _nat _bridge _type _vale
local _id _format="%-15s %-10s %-11s %-9s %-12s %s\n"
__config_load "${vm_dir}/.config/switch"
__config_get "_switchlist" "switch_list"
printf "${_format}" "NAME" "TYPE" "IDENT" "VLAN" "NAT" "PORTS"
for _switch in ${_switchlist}; do
__switch_get_ident "_id" "${_switch}"
[ -z "${_id}" ] && _id="-"
__config_get "_portlist" "ports_${_switch}"
__config_get "_vlan" "vlan_${_switch}"
__config_get "_nat" "nat_${_switch}"
__config_get "_bridge" "bridge_${_switch}"
__config_get "_vale" "vale_${_switch}"
if __checkyesno "${_vale}"; then
_type="vale"
__switch_vale_id "_id" "${_switch}"
: ${_portlist:=n/a}
: ${_vlan:=n/a}
: ${_nat:=n/a}
elif [ -z "${_bridge}" ]; then
_type="auto"
: ${_portlist:=-}
: ${_vlan:=-}
: ${_nat:=-}
else
_type="manual"
_portlist="n/a"
_vlan="n/a"
_nat="n/a"
fi
printf "${_format}" "${_switch}" "${_type}" "${_id}" "${_vlan}" "${_nat}" "${_portlist}"
done
}
# 'vm switch create'
# create a new virtual switch
#
# @param string _switch name of the switch to create
#
__switch_create(){
local _switch="$1"
local _id _curr_list _curr
2015-11-26 19:59:40 +00:00
echo "${_switch}" | egrep -iqs '^[a-z0-9][a-z0-9\_\-\.]{0,14}[a-z0-9]$'
[ $? -ne 0 ] && __err "invalid switch name"
_curr_list=$(sysrc -inqf "${vm_dir}/.config/switch" switch_list)
for _curr in $_curr_list; do
[ "${_switch}" = "${_curr}" ] && __err "switch ${_switch} already exists"
done
__rc_append_string ".config/switch" "switch_list" "${_switch}"
_id=$(ifconfig bridge create)
[ $? -ne 0 ] && __err "failed to create bridge interface"
ifconfig "${_id}" description "vm-${_switch}" up
}
# 'vm switch import'
# use an existing manually configured bridge
#
# @param string _switch name of the switch to create
# @param string _bridge name of the bridge interface to import
#
__switch_import(){
local _switch="$1"
local _bridge="$2"
local _exists
[ -z "${_switch}" -o -z "${_bridge}" ] && __usage
2015-11-26 19:59:40 +00:00
echo "${_switch}" | egrep -iqs '^[a-z0-9][a-z0-9\_\-\.]{0,14}[a-z0-9]$'
[ $? -ne 0 ] && __err "invalid switch name"
_curr_list=$(sysrc -inqf "${vm_dir}/.config/switch" switch_list)
for _curr in $_curr_list; do
[ "${_switch}" = "${_curr}" ] && __err "switch ${_switch} already exists"
done
_exists=$(ifconfig | grep "^${_bridge}: ")
[ -z "${_exists}" ] && __err "${_bridge} does not appear to be a valid existing bridge"
__rc_append_string ".config/switch" "switch_list" "${_switch}"
sysrc -inqf "${vm_dir}/.config/switch" "bridge_${_switch}"="${_bridge}" >/dev/null 2>&1
# attach our description to bridge
# this will allow vm-bhyve to find it and attach guests
ifconfig "${_bridge}" description "vm-${_switch}" up
}
# 'vm switch destroy'
# remove a virtual switch
#
# @param string _switch name of the switch
#
__switch_remove(){
local _switch="$1"
local _id _nat _bridge _vale
__config_load "${vm_dir}/.config/switch"
__config_get "_bridge" "bridge_${_switch}"
__config_get "_nat" "nat_${_switch}"
__config_get "_vale" "vale_${_switch}"
# if manual, leave it there and just remove from our config
if [ -n "${_bridge}" ]; then
ifconfig "${_bridge}" description "" >/dev/null 2>&1
__rc_splice_string ".config/switch" "switch_list" "${_switch}"
sysrc -inxqf "${vm_dir}/.config/switch" "bridge_${_switch}"
exit 0
fi
if [ -z "${_vale}" ]; then
__switch_get_ident "_id" "${_switch}"
__switch_remove_allmembers "${_switch}" "${_id}"
fi
__rc_splice_string ".config/switch" "switch_list" "${_switch}"
sysrc -inxqf "${vm_dir}/.config/switch" "ports_${_switch}" "vlan_${_switch}" \
"nat_${_switch}" "vale_${_switch}" >/dev/null 2>&1
if [ -z "${_vale}" ]; then
if [ -n "${_id}" ]; then
ifconfig "${_id}" destroy >/dev/null 2>&1
[ $? -ne 0 ] && __warn "removed configuration but failed to remove bridge"
else
__warn "removed configuration but failed to remove bridge"
fi
# reset nat if this switch had nat
[ -n "${_nat}" ] && __switch_nat_init
fi
}
# 'vm switch vlan'
# set vlan number for a switch
# all interfaces will be removed, vlan interfaces created, then re-added
#
# @param string _switch name of the switch
# @param int _vlan vlan number (or 0 to switch vlan off)
#
__switch_vlan(){
local _switch="$1"
local _vlan="$2"
[ -z "${_switch}" -o -z "${_vlan}" ] && __usage
__switch_auto "${_switch}"
echo "${_vlan}" | egrep -qs '^[0-9]{1,4}$'
[ $? -ne 0 ] && __err "invalid vlan number"
[ ${_vlan} -ge 4095 ] && __err "invalid vlan number"
# we need to remove everything and re-add as raw interfaces will
# change to vlan or visa-versa
__switch_remove_allmembers "${_switch}"
if [ "${_vlan}" = "0" ]; then
sysrc -inxqf "${vm_dir}/.config/switch" "vlan_${_switch}" >/dev/null 2>&1
else
sysrc -inqf "${vm_dir}/.config/switch" "vlan_${_switch}"="${_vlan}" >/dev/null 2>&1
fi
# put interfaces back in
__switch_add_allmembers "${_switch}"
}
# physically add switch member interfaces
# this will add all configured interfaces to the bridge
# if vlan specified, each real interface will be assigned to a vlan interface
#
# @private
# @param string _switch the switch to configure
# @param optional string _id switch id (eg bridge0) if already known
#
__switch_add_allmembers(){
local _switch="$1"
local _id="$2"
local _portlist _port
if [ -z "${_id}" ]; then
__switch_get_ident "_id" "${_switch}"
[ -z "${_id}" ] && __err "failed to get switch id while adding members"
fi
_portlist=$(sysrc -inqf "${vm_dir}/.config/switch" "ports_${_switch}")
if [ -n "${_portlist}" ]; then
for _port in ${_portlist}; do
__switch_configure_port "${_switch}" "${_id}" "${_port}"
done
fi
}
# physically remove all configured members from a switch
#
# @private
# @param string _switch name of the switch
# @param optional string _id switch id if already known
#
__switch_remove_allmembers(){
local _switch="$1"
local _id="$2"
local _portlist _port
if [ -z "${_id}" ]; then
__switch_get_ident "_id" "${_switch}"
[ -z "${_id}" ] && __err "failed to get switch id while removing members"
fi
_portlist=$(sysrc -inqf "${vm_dir}/.config/switch" "ports_${_switch}")
if [ -n "${_portlist}" ]; then
for _port in ${_portlist}; do
__switch_unconfigure_port "${_switch}" "${_id}" "${_port}"
done
fi
}
# physically adds a port to a virtual switch
# if vlan is configured, we need to create the relevant vlan interface first,
# then add that to the bridge
#
# @private
# @param string _switch the switch to add port to
# @param string _id the switch id
# @param string _member name of the member interface to add
#
__switch_configure_port(){
local _switch="$1"
local _id="$2"
local _member="$3"
local _vlan _vid
_vlan=$(sysrc -inqf "${vm_dir}/.config/switch" "vlan_${_switch}")
if [ -n "${_vlan}" ]; then
__switch_get_ident "_vid" "vlan-${_member}-${_vlan}"
if [ -z "${_vid}" ]; then
_vid=$(ifconfig vlan create)
[ $? -ne 0 ] && __err "failed to create vlan interface"
ifconfig "${_vid}" vlandev "${_member}" vlan "${_vlan}" description "vm-vlan-${_member}-${_vlan}"
fi
ifconfig ${_id} addm ${_vid} >/dev/null 2>&1
else
ifconfig ${_id} addm ${_member} >/dev/null 2>&1
fi
[ $? -ne 0 ] && __err "failed to add member to the virtual switch"
}
# 'vm switch add'
# configure a member interface to a "switch"
# add the interface to bridge and update switch configuration
#
# @param string _switch name of the switch
# @param string _member name of the interface to add
#
__switch_add_member(){
local _switch="$1"
local _member="$2"
local _id
[ -z "${_switch}" -o -z "${_member}" ] && __usage
__switch_auto "${_switch}"
__switch_get_ident "_id" "${_switch}"
[ -z "${_id}" ] && __err "unable to locate virtual switch ${_id}"
__switch_configure_port "${_switch}" "${_id}" "${_member}"
__rc_append_string ".config/switch" "ports_${_switch}" "${_member}"
}
# physically remove a port from a virtual switch / bridge
#
# @private
# @param string _switch the switch to update
# @param string _id the switch id
# @param string _member the interface to remove
#
__switch_unconfigure_port(){
local _switch="$1"
local _id="$2"
local _member="$3"
local _id _vlan _vid _usage
_vlan=$(sysrc -inqf "${vm_dir}/.config/switch" "vlan_${_switch}")
if [ -n "${_vlan}" ]; then
__switch_get_ident "_vid" "vlan-${_member}-${_vlan}"
[ -z "${_vid}" ] && __err "unable to find relevent vlan interface for ${_member}"
ifconfig ${_id} deletem ${_vid} >/dev/null 2>&1
else
ifconfig ${_id} deletem ${_member} >/dev/null 2>&1
fi
[ $? -ne 0 ] && __err "failed to remove member from the virtual switch"
# it's possible a vlan interface may be assigned to multiple switches
# we want to remove the vlan interface if possible, but not if it's
# still assigned to another switch
if [ -n "${_vlan}" -a -n "${_vid}" ]; then
_usage=$(ifconfig -a |grep "member: vm-vlan-${_member}-${_vlan}\$")
[ -z "${_usage}" ] && ifconfig "${_vid}" destroy >/dev/null 2>&1
fi
}
# 'vm switch remove'
# remove a member interface from a switch configuration
# update configuration then remove device from bridge
#
# @param string _switch the switch to update
# @param string _member the interface to remove
#
__switch_remove_member(){
local _switch="$1"
local _member="$2"
local _id
[ -z "${_switch}" -o -z "${_member}" ] && __usage
__switch_auto "${_switch}"
__switch_get_ident "_id" "${_switch}"
[ -z "${_id}" ] && __err "unable to locate virtual switch ${_id}"
__rc_splice_string ".config/switch" "ports_${_switch}" "${_member}"
__switch_unconfigure_port "${_switch}" "${_id}" "${_member}"
}
# 'vm switch nat'
# configure nat on a switch
# this function just deals with the vm-bhyve configuration
#
# @param string _switch the name of the switch
# @param string _nat=on|off whether to switch it on or off
#
__switch_nat(){
local _switch="$1"
local _nat="$2"
[ -z "${_switch}" ] && __usage
__switch_auto "${_switch}"
case "${_nat}" in
off)
sysrc -inxqf "${vm_dir}/.config/switch" "nat_${_switch}"
;;
on)
if ! checkyesno pf_enable; then
__err "pf needs to be enabled for nat functionality"
fi
sysrc -inqf "${vm_dir}/.config/switch" "nat_${_switch}=yes" >/dev/null 2>&1
[ $? -ne 0 ] && __err "failed to store nat configuration"
echo "******"
echo " NAT has been enabled on the specified switch"
echo " A sample dnsmasq configuration has been created in /usr/local/etc/dnsmasq.conf.bhyve"
echo " To enable DHCP on this switch, please install the dnsmasq confguration or merge with your existing."
echo "******"
;;
*)
__err "last option should either be 'on' or 'off' to enable/disable nat functionality"
;;
esac
# reset nat configuration
__switch_nat_init
}
# set the system up for nat usage
# do the actual nat work
# we completely take over dnsmasq and assign it to the required interfaces
# /etc/pf.conf gets an include statement added pointing to a file in
# out .config directory. This file contains a nat rule for each nat switch
#
# @private
#
__switch_nat_init(){
local _pf_rules="${vm_dir}/.config/pf-nat.conf"
local _havenat=0
local _grep _switchlist _nat _net24 _if
local _gw _bnum
# get default gateway
_gw=$(netstat -4rn | grep default | awk '{print $4}')
# basic dnsmasq settings
echo "# vm-bhyve dhcp" > /usr/local/etc/dnsmasq.conf.bhyve
echo "port=0" >> /usr/local/etc/dnsmasq.conf.bhyve
echo "domain-needed" >> /usr/local/etc/dnsmasq.conf.bhyve
echo "no-resolv" >> /usr/local/etc/dnsmasq.conf.bhyve
echo "except-interface=lo0" >> /usr/local/etc/dnsmasq.conf.bhyve
echo "bind-interfaces" >> /usr/local/etc/dnsmasq.conf.bhyve
echo "local-service" >> /usr/local/etc/dnsmasq.conf.bhyve
echo "dhcp-authoritative" >> /usr/local/etc/dnsmasq.conf.bhyve
# reset our pf config and create /etc/pf.conf if needed
echo "# vm-bhyve nat" > "${vm_dir}/.config/pf-nat.conf"
[ ! -e "/etc/pf.conf" ] && touch /etc/pf.conf
# only add our include statement to /etc/pf.conf if it's not already in there somwhere
_grep=$(grep "${_pf_rules}" /etc/pf.conf)
[ -z "${_grep}" ] && echo "include \"${_pf_rules}\"" >> /etc/pf.conf
_switchlist=$(sysrc -inqf "${vm_dir}/.config/switch" switch_list)
# add each nat switch to dnsmasq.conf and .config/pf-nat.conf
for _switch in ${_switchlist}; do
_nat=$(sysrc -inqf "${vm_dir}/.config/switch" "nat_${_switch}")
__switch_get_ident "_id" "${_switch}"
if [ "${_nat}" = "yes" -a -n "${_id}" ]; then
_bnum=$(echo "${_id}" |awk -F'bridge' '{print $2}')
_net24="172.16.${_bnum}"
echo "nat on ${_gw} from {${_net24}.0/24} to any -> (${_gw})" >> "${vm_dir}/.config/pf-nat.conf"
echo "" >> /usr/local/etc/dnsmasq.conf.bhyve
echo "interface=${_id}" >> /usr/local/etc/dnsmasq.conf.bhyve
echo "dhcp-range=${_net24}.10,${_net24}.254" >> /usr/local/etc/dnsmasq.conf.bhyve
# make sure interface has an ip
# this doesn't get removed when nat disabled but not a major issue
ifconfig "${_id}" ${_net24}.1/24
_havenat="1"
fi
done
# make sure forwarding enabled if we have any nat
[ "${_havenat}" = "1" ] && sysctl net.inet.ip.forwarding=1 >/dev/null 2>&1
# restart services regardless
# still need to restart if _havenat=0, in case we've just removed last nat switch
__restart_service "pf"
}
# check if a switch is a vale switch
# if so, we don't need to actually create anything.
# interfaces are created on demand when starting guests,
# and vale switches are dynamic
#
__switch_is_vale(){
local _switch="$1"
local _is_vale=$(sysrc -inqf "${vm_dir}/.config/switch" "vale_${_switch}")
[ -z "${_is_vale}" ] && return 1
if __checkyesno "${_is_vale}"; then
return 0
else
return 1
fi
}
# gets a unique port name for a vale interface
# we need to make sure vale switch name is the same
# for all interfaces on the same switch, but port is
# different
#
# @param string _var name of variable to put port name into
# @param string _switch the name of the switch
# @param string _port unique port identifier (usually mac address)
#
__switch_vale_id(){
local _var="$1"
local _switch="$2"
local _port="$3"
local _id_s _id_p
# get a switch id
_id_s=$(md5 -qs "${_switch}" | awk '{print substr($0,0,5)}')
# given port?
if [ -n "${_port}" ]; then
_id_p=$(md5 -qs "${_port}" | awk '{print substr($0,0,5)}')
setvar "${_var}" "vale${_id_s}:${_id_p}"
else
setvar "${_var}" "vale${_id_s}"
fi
}
# produce an error if the listed switch is not managed by vm-bhyve.
# we allow users to import existing bridges which they configure themselves.
# we don't allow vm-bhyve to mess with these, it's all up to the user
# also don't mess with vale switches
#
# @param string _switch the name of the switch
#
__switch_auto(){
local _switch="$1"
local _bridge _vale
_bridge=$(sysrc -inqf "${vm_dir}/.config/switch" "bridge_${_switch}")
_vale=$(sysrc -inqf "${vm_dir}/.config/switch" "vale_${_switch}")
[ -n "${_bridge}" ] && __err "this is a manual switch that is managed outside of vm-bhyve"
[ -n "${_vale}" ] && __err "this command is not currently supported on vale switches"
}
# get the interface name for a switch
# the id will be the name of the bridge interface (eg. "bridge0")
# ifconfig will have "description: vm-switchname" directly below the first interface line
#
# @param string _var variable to put id into
# @param string _switch the switch to look for
#
__switch_get_ident(){
local _var="$1"
local _switch="$2"
local _c_id
# search ifconfig for our switch id, and pull bridge interface name from preceeding line
# we've got rid of trailing '-' on switch names but on vm-bhyve upgrade, before host reboot, users may still have them
_c_id=$(ifconfig -a | grep -B 1 "vm-${_switch}\$" | head -n 1 | awk -F: '{print $1}')
[ -z "${_c_id}" ] && _c_id=$(ifconfig -a | grep -B 1 "vm-${_switch}-\$" | head -n 1 | awk -F: '{print $1}')
setvar "${_var}" "${_c_id}"
2015-06-30 10:21:30 +01:00
}