aboutsummaryrefslogblamecommitdiffstats
path: root/qemu
blob: c1b0206fd592daf9155c9b260a02391281f65fb8 (plain) (tree)






























































































































































































































                                                                                                      
#!/bin/sh

# routed subnet qemu hook for libvirt (L2 over L3)
#
# https://2e8.dk/libvirt-routed-subnet

# Example XML:
# <domain>
#   <metadata>
#     <routed-subnet xmlns="https://2e8.dk/libvirt-routed-subnet">
#       <interface mac="xx:xx:xx:xx:xx:xx" mtu="9000">
#         <gateway address="192.0.2.0/24"/>
#         <host address="192.0.2.42"/>
#       </interface>
#     </routed-subnet>
#   </metadata>
# </domain>

set -e

routingxml=""
fullxml=""
device=""
dry_run=0

IP="$(which ip)"
ip(){
	if [ $dry_run -eq 0 ] ; then
		"$IP" "$@"
	else
		echo ip "$@"
	fi
}

SYSCTL="/usr/sbin/sysctl"
sysctl(){
	if [ $dry_run -eq 0 ] ; then
		"$SYSCTL" "$@"
	else
		echo sysctl "$@"
	fi
}

xpath(){
	echo "$2" | xmllint --xpath "$1" - 2>/dev/null
}

get_address_family(){ [ -z "${1##*:*}" ] && echo ipv6 || echo ipv4; }

get_config(){
	local configfile="$1"
	local ns="https://2e8.dk/libvirt-routed-subnet"
	fullxml="$(cat "$configfile")"
	routingxml="$(xpath "//*[local-name()='routed-subnet' and namespace-uri()='$ns']" "$fullxml")"
}

get_device(){
	xpath "string(//interface[@type = 'ethernet']/mac[@address='$1']/../target/@dev)" "$fullxml"
}

get_element(){
	xpath "//*[local-name()='$1'][$2]" "$3"
}

get_attr(){
	xpath "string(*/@$1)" "$2"
}

node_loop(){
	local handler="$1"
	shift
	local i=1
	local ret=1
	while [ $i -lt 1000 ]; do
		$handler $i "$@" || return $ret
		i=$(( $i + 1 ))
		ret=0
	done
	return 2
}

process_gateway(){
	local i=$1
	local ifacexml="$2"
	local nodexml="$(get_element 'gateway' "$i" "$ifacexml")"
	if [ "$nodexml" = "" ] ; then
		return 1
	fi

	if [ $dry_run -ne 0 ] ; then
		echo "$nodexml"
	fi

	local address="$(get_attr 'address' "$nodexml")"

	ip addr add "$address" dev "$device"

	return 0
}

process_host(){
	local i=$1
	local ifacexml="$2"
	local nodexml="$(get_element 'host' "$i" "$ifacexml")"
	if [ "$nodexml" = "" ] ; then
		return 1
	fi

	if [ $dry_run -ne 0 ] ; then
		echo "$nodexml"
	fi

	local address="$(get_attr 'address' "$nodexml")"
	local family="$(get_address_family "$address")"

	if [ "$family" = "ipv4" ] ; then
		ip route add proto static "$address/32" dev "$device"
	fi
	return 0
}

process_route(){
	local i=$1
	local ifacexml="$2"
	local nodexml="$(get_element 'route' "$i" "$ifacexml")"
	if [ "$nodexml" = "" ] ; then
		return 1
	fi

	if [ $dry_run -ne 0 ] ; then
		echo "$nodexml"
	fi

	local prefix="$(get_attr 'prefix' "$nodexml")"
	local nexthop="$(get_attr 'nexthop' "$nodexml")"

	ip route add proto static "$prefix" via "$nexthop"
	return 0
}

process_iface(){
	local i=$1
	local ifacexml="$(get_element 'interface' "$1" "$routingxml")"
	if [ "$ifacexml" = "" ] ; then
		return 1
	fi

	local mac="$(xpath "string(/interface/@mac)" "$ifacexml")"
	if [ "$mac" = "" ] ; then
		return 1
	fi

	device="$(get_device "$mac")"
	if [ "$device" = "" ] ; then
		return 1
	fi

	local mtu="$(xpath "string(/interface/@mtu)" "$ifacexml")"
	if [ "$mtu" != "" ] ; then
		ip link set mtu "$mtu" dev "$device"
	fi

	prepare_device
	node_loop process_gateway "$ifacexml" || true
	node_loop process_host "$ifacexml" || true
	node_loop process_route "$ifacexml" || true

	return 0
}

prepare_device(){
	# IPv4
	sysctl -q -w "net.ipv4.conf.$device.proxy_arp=1"
	sysctl -q -w "net.ipv4.conf.$device.rp_filter=1"
	sysctl -q -w "net.ipv4.conf.$device.forwarding=1"

	# IPv6
	sysctl -q -w "net.ipv6.conf.$device.forwarding=1"
	sysctl -q -w "net.ipv6.conf.$device.accept_ra=0"

	# link up
	ip link set up dev "$device"
}

start_network(){
	node_loop process_iface || true
}

usage(){
	echo "Usage: $0 <guest_name> start begin <xmlfile>"
	echo "config: https://2e8.dk/libvirt-routed-subnet"
	echo "libvirt docs: https://www.libvirt.org/hooks.html"
}

test_main(){
	dry_run=1
	main foobar start begin test-in.xml
}

main(){
	if [ $# -eq 1 ] && [ "$1" = 'test' ] ; then
		test_main
		exit $?
	elif [ $# -ne 4 ] ; then
		echo 'too few arguments' >&2
		usage >&2
		exit 1
	fi
	local guest="$1"
	local event="$2"
	local evtype="$3"
	local configfile="$4"
	local state="${event}_${evtype}"
	get_config "$configfile"

	case "$state" in
	start_begin)
		start_network "$@"
		;;
	esac
}

main "$@"