aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAsbjørn Sloth Tønnesen <ast@2e8.dk>2023-08-05 12:15:49 +0000
committerAsbjørn Sloth Tønnesen <ast@2e8.dk>2023-08-06 11:04:16 +0000
commitb2fe8986a66a9a130e41b8b9087458ab3e80816a (patch)
treebf2d651f144b3d0e5be22fc59a942a6123f20446
parentc60a813c855b7fbba0de88fa625cc423585ff353 (diff)
downloadlibvirt-routed-subnet-b2fe8986a66a9a130e41b8b9087458ab3e80816a.tar.gz
libvirt-routed-subnet-b2fe8986a66a9a130e41b8b9087458ab3e80816a.tar.xz
libvirt-routed-subnet-b2fe8986a66a9a130e41b8b9087458ab3e80816a.zip
initial commitHEADmaster
Signed-off-by: Asbjørn Sloth Tønnesen <ast@2e8.dk>
-rw-r--r--README.md7
-rwxr-xr-xqemu223
-rw-r--r--test-in.xml29
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`
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:
+# <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>