diff options
author | Asbjørn Sloth Tønnesen <ast@2e8.dk> | 2023-08-05 12:15:49 +0000 |
---|---|---|
committer | Asbjørn Sloth Tønnesen <ast@2e8.dk> | 2023-08-06 11:04:16 +0000 |
commit | b2fe8986a66a9a130e41b8b9087458ab3e80816a (patch) | |
tree | bf2d651f144b3d0e5be22fc59a942a6123f20446 | |
parent | c60a813c855b7fbba0de88fa625cc423585ff353 (diff) | |
download | libvirt-routed-subnet-b2fe8986a66a9a130e41b8b9087458ab3e80816a.tar.gz libvirt-routed-subnet-b2fe8986a66a9a130e41b8b9087458ab3e80816a.tar.xz libvirt-routed-subnet-b2fe8986a66a9a130e41b8b9087458ab3e80816a.zip |
Signed-off-by: Asbjørn Sloth Tønnesen <ast@2e8.dk>
-rw-r--r-- | README.md | 7 | ||||
-rwxr-xr-x | qemu | 223 | ||||
-rw-r--r-- | test-in.xml | 29 |
3 files changed, 259 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..c017cc1 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +- Install hook into /etc/libvirt/hooks/qemu +- Restart libvirt +- Profit + +## Testing + +Run `./qemu test` @@ -0,0 +1,223 @@ +#!/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 "$@" diff --git a/test-in.xml b/test-in.xml new file mode 100644 index 0000000..59af475 --- /dev/null +++ b/test-in.xml @@ -0,0 +1,29 @@ +<domain type='kvm'> + <name>testhost</name> + <metadata> + <routed-subnet xmlns="https://2e8.dk/libvirt-routed-subnet"> + <interface mac="fa:12:34:56:78:90"> + <gateway address="192.0.2.1/29"/> + <host address="192.0.2.2"/> + <route prefix="192.0.2.128/25" nexthop="192.0.2.2"/> + <gateway address="2001:db8:42::1/64"/> + <host address="2001:db8:42::2"/> + <route prefix="2001:db8:4242::/64" nexthop="2001:db8:42::2"/> + </interface> + <interface mac="fa:12:34:ab:cd:ef" mtu="9000"> + <gateway address="192.0.2.9/29"/> + <host address="192.0.2.10"/> + </interface> + </routed-subnet> + </metadata> + <devices> + <interface type="ethernet"> + <mac address="fa:12:34:56:78:90"/> + <target dev="foo0"/> + </interface> + <interface type="ethernet"> + <mac address="fa:12:34:ab:cd:ef"/> + <target dev="bar0"/> + </interface> + </devices> +</domain> |