Debian

使用 libvirt 為 Linux 橋接虛擬機提供 VLAN 支持

  • February 6, 2022

我正在使用systemd-networkd在所有節點上使用Debian BullseyeKVM(基於核心的虛擬機)配置由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 ---
&lt;metadata&gt;
 &lt;my:home xmlns:my="http://hoeft-online.de/libvirt"&gt;
   &lt;my:iface pvid="26"&gt;
     &lt;my:vlan untagged="yes"&gt;26&lt;/my:vlan&gt;
     &lt;my:vlan&gt;50&lt;/my:vlan&gt;
     &lt;my:vlan untagged="no"&gt;30&lt;/my:vlan&gt;
   &lt;/my:iface&gt;
   &lt;my:iface&gt;
     &lt;my:vlan untagged="yes"&gt;50&lt;/my:vlan&gt;
     &lt;my:vlan&gt;10&lt;/my:vlan&gt;
   &lt;/my:iface&gt;
 &lt;/my:home&gt;
&lt;/metadata&gt;
--- snap ---

正如文件所給出的,我必須使用我自己的自定義命名空間&lt;my:home xmlns:my="http://hoeft-online.de/libvirt"&gt;。一旦創建,在命名空間中工作就更容易了:

host ~$ virsh metadata guest-vm http://hoeft-online.de/libvirt [--edit --key my]
&lt;home&gt;
 &lt;iface pvid="26"&gt;
   &lt;vlan untagged="yes"&gt;26&lt;/vlan&gt;
   &lt;vlan&gt;50&lt;/vlan&gt;
   &lt;vlan untagged="no"&gt;30&lt;/vlan&gt;
 &lt;/iface&gt;
 &lt;iface&gt;
   &lt;vlan untagged="yes"&gt;50&lt;/vlan&gt;
   &lt;vlan&gt;10&lt;/vlan&gt;
 &lt;/iface&gt;
&lt;/home&gt;

步驟 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
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!-- 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)
--&gt;
&lt;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"&gt;
 &lt;xsl:output omit-xml-declaration="yes" indent="no"
      encoding="utf-8" media-type="text/xml"/&gt;
 &lt;xsl:strip-space elements="*"/&gt;
 &lt;xsl:template match="text()|@*"/&gt;


 &lt;xsl:template match="/domain"&gt;
   &lt;meta&gt;
     &lt;xsl:apply-templates/&gt;
   &lt;/meta&gt;
 &lt;/xsl:template&gt;


 &lt;xsl:template match='*'&gt;
   &lt;xsl:for-each select='interface[@type="bridge"]/target'&gt;
   &lt;iface&gt;
     &lt;xsl:variable name="_index" select="position()" /&gt;

     &lt;xsl:attribute name="pvid"&gt;
       &lt;xsl:choose&gt;
         &lt;xsl:when test="/*/metadata/my:home/my:iface[$_index]/@pvid != ''"&gt;
           &lt;xsl:value-of select="/*/metadata/my:home/my:iface[$_index]/@pvid"/&gt;
         &lt;/xsl:when&gt;
         &lt;xsl:otherwise&gt;
           &lt;xsl:text&gt;0&lt;/xsl:text&gt;
         &lt;/xsl:otherwise&gt;
       &lt;/xsl:choose&gt;
     &lt;/xsl:attribute&gt;

     &lt;xsl:value-of select="@dev"/&gt;

     &lt;xsl:for-each select="/*/metadata/my:home/my:iface[$_index]/my:vlan"&gt;
       &lt;vlan&gt;
         &lt;xsl:attribute name="untagged"&gt;
           &lt;xsl:choose&gt;
             &lt;xsl:when test="@untagged='yes'"&gt;
               &lt;xsl:text&gt;yes&lt;/xsl:text&gt;
             &lt;/xsl:when&gt;
             &lt;xsl:otherwise&gt;
               &lt;xsl:text&gt;no&lt;/xsl:text&gt;
             &lt;/xsl:otherwise&gt;
           &lt;/xsl:choose&gt;
         &lt;/xsl:attribute&gt;

         &lt;xsl:value-of select="."/&gt;
       &lt;/vlan&gt;
     &lt;/xsl:for-each&gt;

   &lt;/iface&gt;
   &lt;/xsl:for-each&gt;
 &lt;/xsl:template&gt;

&lt;!-- vim: set sts=2 sw=2 et autoindent nowrap: --&gt;
&lt;/xsl:stylesheet&gt;

第 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):
#
# &lt;?xml version="1.0"?&gt;
# &lt;meta&gt;
#   &lt;iface pvid="26"&gt;vnet1
#     &lt;vlan untagged="yes"&gt;26&lt;/vlan&gt;
#     &lt;vlan untagged="no"&gt;50&lt;/vlan&gt;
#     &lt;vlan untagged="no"&gt;30&lt;/vlan&gt;
#   &lt;/iface&gt;
#   &lt;iface pvid="30"&gt;vnet2
#     &lt;vlan untagged="yes"&gt;30&lt;/vlan&gt;
#     &lt;vlan untagged="no"&gt;50&lt;/vlan&gt;
#   &lt;/iface&gt;
#   &lt;iface pvid="0"&gt;vnet3&lt;/iface&gt;
# &lt;/meta&gt;

# for DEBUG uncomment/comment next three lines
#exec 0&lt; 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' &gt;&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" &gt;&2
           exit 1
       fi
       if [[ ! -x "$XMLPROG" ]]; then
           echo "Error: $XMLPROG is not executable" &gt;&2
           exit 1
       fi

       #cat - &gt;/var/log/libvirt/start-"$1".xml; exit 1   # get live xml for DEBUG
       META=$("$XMLPROG" tr "$XSLFILE" -)
       #echo "DEBUG: using hook start with $META" &gt;&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 $*" &gt;&2
       exit 1
       ;;
esac

# vim: set sts=4 sw=4 et autoindent nowrap:

引用自:https://serverfault.com/questions/1051294