使用 libvirt 为 Linux 桥接虚拟机提供 VLAN 支持

使用 libvirt 为 Linux 桥接虚拟机提供 VLAN 支持

我在用着systemd-networkd配置由管理的网络接口虚拟器为了虚拟机(基于内核的虚拟机)Debian Bullseye在所有节点上。我希望使用Linux 桥接器。对于 Linux Bridge,不支持虚拟器

例如,我有一个虚拟机,它有三个接口连接到网桥:

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*客户机的其他接口,以便客户机可以使用它们?

答案1

如何通过标记的 VLAN 桥将 libvirt 虚拟机连接到另一个网络

这是基于 Redhat/CentOS 系统的类似设置,使用 systemd-networkd,无需 shell 脚本。这显示了如何将 libvirt/KVM vm 连接到网络,客户机将自动收到动态 IP 地址。服务器将不会在此网络上分配 IP 地址,因此网络内的其他计算机都无法看到服务器(例如,对于 Windows vm 来说,它可能是一个不受信任的网络,用于与网络扫描仪、打印机或某些需要您使用仅在 Windows 上运行的有缺陷的软件的设备进行通信)。

在此示例中,服务器有一个名为“lan3”的物理接口,该接口连接到交换机端口,该端口是多个 VLAN 的(带标记)成员。每个 VLAN 都有自己的子网和 dhcp 服务器,例如:“lab1”vid 1910,子网 192.168.10.0/24;“lab3”vid 1903,子网 192.168.3.0/24...

目标:

  • 服务器网卡“lan3”=上行链路
  • 使用 systemd-networkd 的服务器。
  • 没有 VLAN 标签的 lan3 无法通过 dhcp 访问任何网络,因此没有分配动态地址
  • 服务器使用“lan1”或其他网卡来处理其他所有事情(不相关)。
  • 上游路由器或L3交换机提供多个网络,其中一个是“lab3”。
  • 交换机端口提供这些网络(标记成员)。
  • 服务器不能接收此附加网络上的 IP。
  • 运行 Windows 的客户虚拟机应该在这个“lab3”网络上接收一个 IP。
  • 无需手动交互,无需脚本,只要 systemd-networkd 工作,它就应该工作。

连接:

lan3 (phy) -> br3 (neutral) -> eth1930 (vlan) -> br_lab3 (vm bridge) -> vm

NIC 配置 - 上行链路:

# lan3.network

[Match]
Name=lan3

[Network]
Bridge=br3

NIC 配置-上行桥接设备:

# br3.netdev 

[NetDev]
Name=br3
Kind=bridge

NIC 配置 - 具有 VLAN 成员资格的上行链路桥接网络:

# br3.network

[Match]
Name=br3

[Network]
VLAN=eth.1930

NIC 配置 - VLAN 设备:

# eth.1930.netdev 

[NetDev]
Name=eth.1930
Kind=vlan

[VLAN]
Id=1930

NIC 配置 - VLAN 网络:

# eth.1930.network 

[Match]
Name=eth.1930

[Network]
Bridge=br_lab3

# No IP for this server
[Network]
DHCP=no

NIC 配置-虚拟机桥接设备:

# br_lab3.netdev 

[NetDev]
Name=br_lab3
Kind=bridge

NIC 配置-虚拟机桥接网络:

# br_lab3.network 
[Match]
Name=br_lab3

[Network]
DHCP=no

虚拟机配置:

# bridge device = br_lab3

解释:

  • 上行端口“lan3”未标记,未分配 IP。
  • 上行链路桥接器“br3”一端连接到物理上行链路端口,另一端连接到通用名称为“eth.1930”的 VLAN。此 VLAN 将允许我们加入“lab3”网络。可以在此处添加其他 VLAN...
  • 然后将此 VLAN 连接到虚拟机桥“br_lab3”。
  • vm 桥接器“br_lab3”连接到 vm(网络源 = 桥接设备)。
  • 该虚拟机内部运行的客户系统将从提供网络的上行链路路由器接收动态 IP 地址。

virt-manager 中的 VM NIC 配置(vm bridge)

答案2

我已经研究过这个问题并找到了解决方案。手动将 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 钩子脚本我将使用钩子脚本库姆并分三步进行。

步骤 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 配置中获取启动信息

钩子脚本virsh dumpxml guest-vm获取有关其标准输入的信息。这是正在运行的 VM 的 XML 配置。我们还可以在 VM 运行时使用。我使用 XSLT 和xmlstarlet使用 xsl-stylesheet 获取所需信息。我可以使用以下方法进行测试:

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 ([email protected])
     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 ([email protected])
# 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:

答案3

感谢@Ingo 的想法,虽然他的(可能的)解决方案很有效,但我并不想使用 xmlstarlet,因为它不在 Centos Stream 9 的存储库中。

无论如何,我使用了 Ingos 的想法并用 Python 实现了它。唯一需要的就是 Python。

https://github.com/modzilla99/libvirt-vlans

相关内容