#!/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 "$@"