使用套接字网络后端创建QEMU桥

2024-05-21 05:25:12 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试在QEMU中创建一个到主机接口的网桥,很像Virtualbox和VMWare的网桥适配器,使用套接字网络和一个名为Scapy的Python库的组合(基本上,在幕后依赖Windows操作系统上的WinPcap/Npcap或Unix操作系统上的libpcap)。下面是我创建的桥接脚本,用于将QEMU的socket网络后端创建的VLAN连接到外部接口:

import argparse
import scapy
import threading
import socket
import struct
import scapy.sendrecv
import scapy.packet
import scapy.config
import scapy.layers.l2

MAX_PACKET_SIZE = 65535

send_lock = threading.Lock()
qemu_senders = set()
iface_senders = set()


def qemu_in_iface_out_traffic_thread_func(iface, mcast_addr, mcast_port, local_addr):
    global MAX_PACKET_SIZE
    global send_lock
    global qemu_senders
    global iface_senders

    # Create the multicast listen socket.
    listener_addr = (local_addr, mcast_port)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(listener_addr)
    mcast_group = socket.inet_aton(mcast_addr)
    mreq = struct.pack('4sL', mcast_group, socket.INADDR_ANY)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

    # Get the packets from the QEMU VLAN, and send them over to the host's interface.
    while True:
        data, _ = sock.recvfrom(MAX_PACKET_SIZE)
        send_lock.acquire()
        eth_pkt = scapy.layers.l2.Ether(data)
        if eth_pkt.src not in iface_senders:
            qemu_senders.add(eth_pkt.src)
            scapy.sendrecv.sendp(eth_pkt, iface=iface, verbose=0)
        send_lock.release()


def iface_in_qemu_out_traffic_thread_func(iface, mcast_addr, mcast_port):
    global send_lock
    global qemu_senders
    global iface_senders

    # Create the multicast send socket.
    mcast_group = (mcast_addr, mcast_port)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    ttl = struct.pack('b', 1)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)

    # Sniff packets from the host's interface, and send them to the QEMU VLAN.
    def process_packet(eth_pkt):
        send_lock.acquire()
        if eth_pkt.src not in qemu_senders:
            iface_senders.add(eth_pkt.src)
            sock.sendto(scapy.packet.Raw(eth_pkt).load, mcast_group)
        send_lock.release()
    scapy.sendrecv.sniff(iface=iface, prn=process_packet, store=0)


if __name__ == "__main__":
    # Parse the command line arguments.
    parser = argparse.ArgumentParser()
    parser.add_argument('--iface', '-i', required=True)
    parser.add_argument('--mcast-addr', '-a', required=True)
    parser.add_argument('--mcast-port', '-p', required=True, type=int)
    parser.add_argument('--local-addr', '-l', default='127.0.0.1')
    parser.add_argument('--disable-promisc', '-d',
                        default=False, action='store_true')
    args = parser.parse_args()

    # Set promiscuous mode.
    scapy.config.conf.sniff_promisc = 0 if args.disable_promisc else 1

    # Create the traffic threads.
    qemu_in_iface_out_traffic_thread = \
        threading.Thread(target=qemu_in_iface_out_traffic_thread_func, args=(
            args.iface, args.mcast_addr, args.mcast_port, args.local_addr
        ))
    iface_in_qemu_out_traffic_thread = \
        threading.Thread(target=iface_in_qemu_out_traffic_thread_func, args=(
            args.iface, args.mcast_addr, args.mcast_port
        ))

    # Run the traffic threads, and join them to wait for their exit.
    qemu_in_iface_out_traffic_thread.start()
    iface_in_qemu_out_traffic_thread.start()
    qemu_in_iface_out_traffic_thread.join()
    iface_in_qemu_out_traffic_thread.join()

有了这个桥接源代码,我能够在我的主机之外的设备(例如Raspberry Pi控制器)和我的QEMU VM之间来回ping,我的控制器和主机都位于同一个LAN上。但是,我无法在同一网络上的QEMU VM和主机之间执行相同的操作。我想知道问题是否与两个不同的MAC地址(即我的QEMU VM的MAC地址和我的主机的MAC地址)具有相同的接口(连接到LAN的主机接口)以及到同一LAN的流量被过滤掉有关,或者我在这里遗漏了什么

编辑#1:

因此,我检查了QEMU VM或我的主机在网络上发送/接收的数据包,它们在网络上的所有设备上都被接收。我已将一个路由器连接到我的网络,并将另一台带有Wi-Fi和Wireshark的笔记本电脑设备通过路由器连接到网络。我可以看到在网络的所有端接收到的数据包(例如,在网络上的新笔记本电脑上),但我的主机不响应ARP数据包,例如,由同一接口上的另一台机器(如QEMU VM)发起的数据包

以下是分别从主机和网络上的新笔记本电脑上截取的Wireshark屏幕截图:

enter image description here

enter image description here

从上面截图中可以看出,我的主机之外的任何设备(192.168.1.1的路由器、Rasberry Pi控制器和其他笔记本电脑)都可以与我的QEMU VM对话并回复ARP请求(同样,QEMU VM也可以),但主机不能

以下是我所看到的观察结果(为这张糟糕的画道歉):

enter image description here

但是,如果我用Virtualbox虚拟机替换QEMU虚拟机,主机和Virtualbox虚拟机之间就会有连接。此外,QEMU虚拟机也不能与主机内的任何机器进行对话,例如QEMU虚拟机和Virtualbox虚拟机之间的对话,两者都通过主机接口连接到同一LAN

编辑#2:

我观察到的另一件事是,当从VirtualBox虚拟机ARP我的主机时,我不会在Wireshark的同一界面上看到主机对虚拟机的ARP回复(就像上面的症状一样)。我认为VirtualBox的NetFilter主机驱动程序负责创建网桥,它创建了另一个网络,主机和虚拟机的请求和响应在该网络中交换,而主机和虚拟机在真实界面上与外部世界通信。显然,我需要创建自己的主机驱动程序,将数据包注入从QEMU VM通过网桥发送的接收流中


Tags: in网络sendargssocketoutthreadscapy