使用 libvirt 為 Linux 橋接虛擬機提供 VLAN 支持
我正在使用systemd-networkd在所有節點上使用Debian Bullseye為KVM(基於核心的虛擬機)配置由libvirt管理的網路介面。我想在使用Linux Bridge的虛擬機上支持透明 VLAN 。對於 Linux Bridge,這不受libvirt支持。
例如,我有一個虛擬機,其三個介面連接到網橋:
host ~$ virsh attach-interface guest-vm bridge br0 --config host ~$ virsh attach-interface guest-vm bridge br0 --config host ~$ virsh attach-interface guest-vm bridge br0 --config
現在,當執行來賓時,我將在橋上看到:
host ~$ sudo bridge link 3: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 4 30: vnet13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 100 31: vnet14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 100 32: vnet15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 100
所有介面都成功連接到網橋。enp1s0是主機上的上行鏈路介面。現在查看網橋上的 VLAN id 時,我看到:
~$ sudo bridge vlan port vlan-id enp1s0 10 26 30 50
僅顯示主機介面及其 VLAN id。
有沒有辦法將 VLAN id 附加到
vnet*
來賓的其他介面,以便它可以使用它們?
我已經解決了這個問題並找到了解決方案。手動添加 VLAN id 到網橋的從介面是沒有問題的,例如:
host ~$ sudo bridge vlan add vid 26 dev vnet13 pvid 26 untagged host ~$ sudo bridge vlan add vid 30 dev vnet13 host ~$ sudo bridge vlan add vid 50 dev vnet14 host ~$ $ sudo bridge vlan port vlan-id enp1s0 10 26 30 50 vnet13 26 PVID Egress Untagged 30 vnet14 50
問題是在虛擬機啟動時自動執行此操作。幸運的是libvirt支持libvirt 掛鉤腳本。我將使用qemu的鉤子腳本並分三步完成。
第 1 步:定義哪個 VLAN-ID 連接到哪個介面
為此,我們為自定義元數據提供了域 XML 格式的額外元素 <metadata>。我們可以簡單地將資訊添加到域(虛擬機)的靜態配置中:
host ~$ virsh edit guest-vm --- snip --- <metadata> <my:home xmlns:my="http://hoeft-online.de/libvirt"> <my:iface pvid="26"> <my:vlan untagged="yes">26</my:vlan> <my:vlan>50</my:vlan> <my:vlan untagged="no">30</my:vlan> </my:iface> <my:iface> <my:vlan untagged="yes">50</my:vlan> <my:vlan>10</my:vlan> </my:iface> </my:home> </metadata> --- snap ---
正如文件所給出的,我必須使用我自己的自定義命名空間
<my:home xmlns:my="http://hoeft-online.de/libvirt">
。一旦創建,在命名空間中工作就更容易了:host ~$ virsh metadata guest-vm http://hoeft-online.de/libvirt [--edit --key my] <home> <iface pvid="26"> <vlan untagged="yes">26</vlan> <vlan>50</vlan> <vlan untagged="no">30</vlan> </iface> <iface> <vlan untagged="yes">50</vlan> <vlan>10</vlan> </iface> </home>
步驟 2:從域的執行時 XML-config 獲取啟動資訊
掛鉤腳本獲取有關其標準輸入的資訊。這是正在執行的 VM 的 XML 配置。我們也可以
virsh dumpxml guest-vm
在虛擬機執行時獲取它。我將 XSLT 與xmlstarlet一起使用,以通過 xsl 樣式表獲取所需的資訊。我可以測試:host ~$ virsh dumpxml guest-vm | xmlstarlet transform /etc/libvirt/hooks/qemu.xsl | xmlstarlet format
這是樣式表:
host ~$ cat /etc/libvirt/hooks/qemu.xsl <?xml version="1.0" encoding="UTF-8"?> <!-- This stylesheet processes the live XML configuration output from a virtual machine managed by libvirt. It transforms the custom metadata information together with attached interfaces and returns a normalized XML with VLAN ids attached to the interface. For further information look at the README file. Author: 2021-01-26 - Ingo Höft (Ingo@Hoeft-online.de) Licence: GPLv3 (https://www.gnu.org/licenses/gpl-3.0.en.html) --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="http://hoeft-online.de/libvirt" exclude-result-prefixes="my"> <xsl:output omit-xml-declaration="yes" indent="no" encoding="utf-8" media-type="text/xml"/> <xsl:strip-space elements="*"/> <xsl:template match="text()|@*"/> <xsl:template match="/domain"> <meta> <xsl:apply-templates/> </meta> </xsl:template> <xsl:template match='*'> <xsl:for-each select='interface[@type="bridge"]/target'> <iface> <xsl:variable name="_index" select="position()" /> <xsl:attribute name="pvid"> <xsl:choose> <xsl:when test="/*/metadata/my:home/my:iface[$_index]/@pvid != ''"> <xsl:value-of select="/*/metadata/my:home/my:iface[$_index]/@pvid"/> </xsl:when> <xsl:otherwise> <xsl:text>0</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:value-of select="@dev"/> <xsl:for-each select="/*/metadata/my:home/my:iface[$_index]/my:vlan"> <vlan> <xsl:attribute name="untagged"> <xsl:choose> <xsl:when test="@untagged='yes'"> <xsl:text>yes</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>no</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:value-of select="."/> </vlan> </xsl:for-each> </iface> </xsl:for-each> </xsl:template> <!-- vim: set sts=2 sw=2 et autoindent nowrap: --> </xsl:stylesheet>
第 3 步:將 VLAN-ID 設置為動態虛擬網路介面 vnet *
使用樣式表中的資訊,我們現在可以使用掛鉤腳本設置網路介面。使其可執行。
harley$ cat /etc/libvirt/hooks/qemu #!/usr/bin/bash # /etc/libvirt/hooks/qemu # Docs: https://www.libvirt.org/hooks.html # Author: 2021-01-26 - Ingo Höft (Ingo@Hoeft-online.de) # Licence: GPLv3 (https://www.gnu.org/licenses/gpl-3.0.en.html) # If you save a modified hook script then do 'sudo systemctl restart libvirtd'. # This script adds VLAN support to interfaces of libvirt guests on start up. # For more details look at the README file. # Most work is done with the powerful XML transformation of the XML # configuration of the guest on stdin with qemu.xsl to get a normalized # meta information into $META, for example like this # (we need it to understand the script): # # <?xml version="1.0"?> # <meta> # <iface pvid="26">vnet1 # <vlan untagged="yes">26</vlan> # <vlan untagged="no">50</vlan> # <vlan untagged="no">30</vlan> # </iface> # <iface pvid="30">vnet2 # <vlan untagged="yes">30</vlan> # <vlan untagged="no">50</vlan> # </iface> # <iface pvid="0">vnet3</iface> # </meta> # for DEBUG uncomment/comment next three lines #exec 0< start-vdeb11-base02.xml # for DEBUG: read testfile to stdin #BRIDGE="/usr/bin/echo" BRIDGE="/usr/sbin/bridge" # and call it with ./qemu "dummy-vm" "start" "begin" "-" XSLFILE="/etc/libvirt/hooks/qemu.xsl" XMLPROG="/usr/bin/xmlstarlet" #GUEST=$1 # name of guest being started OPERATION=$2 SUB_OPERATION=$3 EXTRA_PARM=$4 #echo 'DEBUG: entering qemu.hook' >&2 case "$OPERATION" in prepare) ;; start) if [[ "$SUB_OPERATION" != "begin" ]] || [[ "$EXTRA_PARM" != "-" ]]; then echo "Error: Unhandled parameter \$3='$SUB_OPERATION' or \$4='$EXTRA_PARM' to $0 \$1 \$2 \$3 \$4" >&2 exit 1 fi if [[ ! -x "$XMLPROG" ]]; then echo "Error: $XMLPROG is not executable" >&2 exit 1 fi #cat - >/var/log/libvirt/start-"$1".xml; exit 1 # get live xml for DEBUG META=$("$XMLPROG" tr "$XSLFILE" -) #echo "DEBUG: using hook start with $META" >&2 # loop through interfaces IFACE_COUNT=0 while true; do ((++IFACE_COUNT)) IFACE=$(echo "$META" | "$XMLPROG" sel -t -c "/meta/iface[$IFACE_COUNT]/text()") if [[ -z "$IFACE" ]]; then # finished, no more interfaces available exit 0 fi "$BRIDGE" link set dev "$IFACE" flood off # loop through vlans on one interface VLAN_COUNT=0 while true; do ((++VLAN_COUNT)) VLAN=$(echo "$META" | "$XMLPROG" sel -t -v "/meta/iface[$IFACE_COUNT]/vlan[$VLAN_COUNT]/text()") if [[ -z "$VLAN" ]]; then # finished, no more vlans available, process next interface break else UNTAGGED=$(echo "$META" | "$XMLPROG" sel -t -v "/meta/iface[$IFACE_COUNT]/vlan[$VLAN_COUNT]/@untagged") if [[ "$UNTAGGED" == "yes" ]]; then "$BRIDGE" vlan add vid "$VLAN" dev "$IFACE" pvid "$VLAN" untagged else "$BRIDGE" vlan add vid "$VLAN" dev "$IFACE" fi fi done done ;; started) ;; stopped) ;; release) ;; migrate) ;; restore) ;; reconnect) ;; attach) ;; *) echo "Error: qemu hook called with unexpected options $*" >&2 exit 1 ;; esac # vim: set sts=4 sw=4 et autoindent nowrap: