From b2fe8986a66a9a130e41b8b9087458ab3e80816a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Sloth=20T=C3=B8nnesen?= Date: Sat, 5 Aug 2023 12:15:49 +0000 Subject: initial commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Asbjørn Sloth Tønnesen --- README.md | 7 ++ qemu | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test-in.xml | 29 ++++++++ 3 files changed, 259 insertions(+) create mode 100644 README.md create mode 100755 qemu create mode 100644 test-in.xml 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` diff --git a/qemu b/qemu new file mode 100755 index 0000000..c1b0206 --- /dev/null +++ b/qemu @@ -0,0 +1,223 @@ +#!/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 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 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 "$@" 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 @@ + + testhost + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.1