#!/bin/sh
# routed subnet qemu hook for libvirt (L2 over L3)
#
# https://2e8.dk/libvirt-routed-subnet
# Example XML:
#
#
#
#
#
#
#
#
#
#
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 tag = 'routed-subnet'
local ns='https://2e8.dk/libvirt-routed-subnet'
local query = "//*[local-name()='$tag' and namespace-uri()='$ns']"
fullxml="$(cat "$configfile")"
routingxml="$(xpath "$query" "$fullxml" || true)"
if [ -z "$routingxml" ] ; then
exit 0
fi
}
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 start begin "
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 "$@"