我正在尝试创建虚拟网络设备以测试和开发多播程序。我的电脑有一个网卡和一个以太网端口,可以连接到互联网。我想要第二个(虚拟)NIC,连接到桥接器,该桥接器连接有另外 2 台计算机以进行测试。换句话说:
- 创建虚拟网卡。
- 创建虚拟网桥/交换机。
- 将虚拟网卡连接到虚拟网桥。
- 创建两个额外的虚拟 NIC 设备(用作远程主机)并将它们连接到虚拟桥。
据我了解,在 Linux 中创建虚拟桥会隐式创建虚拟网卡并将其连接到它,该虚拟网卡可作为网络接口进行访问。我回答了一个问题解释了这一点这里(虽然我可能是错的)。
我知道我可以使用虚拟机测试多播程序,但这非常麻烦,而且我的理解是,使用正确的路由表,如果我将它们绑定到适当的虚拟网络设备和地址,我应该能够本地运行程序。到目前为止,我什至无法执行 ping 操作,更不用说多播了。这就是我所拥有的:
ip link add br0 type bridge
ip link add dum0 type dummy
ip link add dum1 type dummy
ip link set dev dum0 master br0
ip link set dev dum1 master br0
ip addr add 10.0.0.1/24 brd + dev br0
ip addr add 10.0.0.2/24 brd + dev dum0
ip addr add 10.0.0.3/24 brd + dev dum1
ip link set br0 up
ip link set dum0 up
ip link set dum1 up
ip route del 10.0.0.0/24 dev dum0
ip route del 10.0.0.0/24 dev dum1
ip route del broadcast 10.0.0.0 dev dum0
ip route del broadcast 10.0.0.0 dev dum1
ip route del broadcast 10.0.0.255 dev dum0
ip route del broadcast 10.0.0.255 dev dum1
ip route del local 10.0.0.2
ip route del local 10.0.0.3
为了方便起见,您可以使用以下命令来撤消该操作:
ip link del dev dum1
ip link del dev dum0
ip link del dev br0
经检查,所有配置均与物理硬件完全相同:
$ ip addr show br0
41: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 56:47:31:fd:10:c0 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/24 brd 10.0.0.255 scope global br0
valid_lft forever preferred_lft forever
inet6 fe80::5447:31ff:fefd:10c0/64 scope link
valid_lft forever preferred_lft forever
$ ip addr show dum0
42: dum0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN group default qlen 1000
link/ether 56:47:31:fd:10:c0 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.2/24 brd 10.0.0.255 scope global dum0
valid_lft forever preferred_lft forever
inet6 fe80::5447:31ff:fefd:10c0/64 scope link
valid_lft forever preferred_lft forever
$ ip addr show dum1
43: dum1: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN group default qlen 1000
link/ether d2:47:c8:19:4a:60 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.3/24 brd 10.0.0.255 scope global dum1
valid_lft forever preferred_lft forever
inet6 fe80::d047:c8ff:fe19:4a60/64 scope link
valid_lft forever preferred_lft forever
$ ip route show table main
10.0.0.0/24 dev br0 proto kernel scope link src 10.0.0.1
$ ip route show table local
broadcast 10.0.0.0 dev br0 proto kernel scope link src 10.0.0.1
local 10.0.0.1 dev br0 proto kernel scope host src 10.0.0.1
broadcast 10.0.0.255 dev br0 proto kernel scope link src 10.0.0.1
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
$ ip route get to 10.0.0.1
local 10.0.0.1 dev lo src 10.0.0.1 uid 1000
cache <local>
$ ip route get to 10.0.0.2
10.0.0.2 dev br0 src 10.0.0.1 uid 1000
cache
dum0
...但有一个例外:和的 MAC 地址br0
相同。这让我很担心,因为这表明我对桥接设备的理解是错误的,它实际上并不是连接到桥接设备的虚拟网卡,而是某种奇怪的既不是桥接器也不是网卡的东西,不能被正常使用。无论如何,我认为这不会干扰其余的测试。通过虚拟设备进行路由也不起作用。
至于测试,我只能通过环回设备 ( ) ping 任何设备lo
。路由表正确地将数据包路由br0
到dum0
和dum1
,但它返回Destination Host Unreachable
:
$ ping -c 2 10.0.0.1 # br0 through lo OK
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.029 ms
--- 10.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 56ms
rtt min/avg/max/mdev = 0.029/0.041/0.053/0.012 ms
$ ping -c 2 10.0.0.2 # dum0 through br0 BAD
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
From 10.0.0.1 icmp_seq=1 Destination Host Unreachable
From 10.0.0.1 icmp_seq=2 Destination Host Unreachable
--- 10.0.0.2 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 62ms
pipe 2
$ ping -c 2 -I lo 10.0.0.2 # dum0 through lo OK
ping: Warning: source address might be selected on device other than lo.
PING 10.0.0.2 (10.0.0.2) from x.x.x.x lo: 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.033 ms
--- 10.0.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 35ms
rtt min/avg/max/mdev = 0.033/0.040/0.047/0.007 ms
此时我真的不知道我可能做错了什么。我通过防火墙修补了所有内容。我认为唯一的就是虚拟设备。我尝试研究如何“仅创建虚拟网卡”,但结果非常令人沮丧。手册ip-link(8)
页实际上列出了数十种可能的设备,完全不知道它们之间的功能有何不同,也不知道您何时会使用它们。我无法强调我多么努力地研究这个问题,因为它看起来很简单,但很难找到有关它的信息(如果你还不知道的话)。
我读过,虚拟设备可能会简单地丢弃数据(来自一个模糊的来源,而不是其他地方),在这种情况下,它们可能会丢弃 ARP 请求,而我找不到它们的 MAC 地址(如果在这个虚拟配置中甚至有必要的话)。我也尝试使用ip tuntap
(Linux Taps),但这也不起作用,但如果我理解正确的话,它们会向从内核请求它们的程序提供原始 IP 数据包(用于隧道)或以太网帧(用于 Taps),否则也会删除所有数据。
那么,我需要什么样的设备?这甚至可以用来测试多播节目吗?我是否能够绑定到设备的地址,从该设备发送多播流量,将其通过网桥发送,并由绑定到另一个设备地址的多播程序接收?这非常复杂,所以我感谢任何帮助和任何可以阅读它的人。谢谢!
答案1
正如@AB 建议的,解决方案是使用多个网络命名空间。我们可以将主机的网络堆栈视为一个进程:接收->处理->输出。 Linux 不允许将输出循环回输入,因此即使我原始配置中的路由是正确的,数据包也会被丢弃——只有一个网络堆栈,并且输出数据包无法再次由同一网络堆栈处理。使用网络命名空间可以创建多个网络堆栈,然后可以根据需要响应 ARP 请求、ping 和多播流量。
link类型veth
可用于创建以太网对,以便每个veth
网络设备代表链路的一端(或者更准确地说,连接到以太网电缆一端的虚拟以太网网络设备)。一端保留在默认网络命名空间中并添加到虚拟桥中,而另一端则添加到创建的网络命名空间中。这允许命名空间之间的通信!这是代码:
ip link add br0 type bridge mcast_snooping 1 mcast_router 2
ip netns add net0
ip link add veth0 type veth peer name veth
ip link set veth netns net0
ip link set dev veth0 master br0
ip netns add net1
ip link add veth1 type veth peer name veth
ip link set veth netns net1
ip link set dev veth1 master br0
ip addr add 10.0.0.1/24 brd + dev br0
ip link set br0 up
ip link set veth0 up
ip link set veth1 up
ip netns exec net0 ip addr add 10.0.0.2/24 brd + dev veth
ip netns exec net1 ip addr add 10.0.0.3/24 brd + dev veth
ip -all netns exec ip link set lo up
ip -all netns exec ip link set veth up
您可以使用以下命令来撤消该操作:
ip link del dev veth1
ip link del dev veth0
ip link del dev br0
ip netns del net1
ip netns del net0
这将创建一个虚拟网桥 ( br0
) 和两个虚拟以太网对(veth0
toveth
和veth1
to veth
),并将veth
设备添加到单独的网络命名空间(在任何名称冲突之前)。在这里我们可以看到结果:
$ ip addr show br0
25: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 1a:96:25:a0:43:c3 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/24 brd 10.0.0.255 scope global br0
valid_lft forever preferred_lft forever
inet6 fe80::3c91:4be6:d418:e045/64 scope link
valid_lft forever preferred_lft forever
$ ip addr show veth0
27: veth0@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
link/ether 1a:96:25:a0:43:c3 brd ff:ff:ff:ff:ff:ff link-netns net0
inet6 fe80::3c91:4be6:d418:e045/64 scope link
valid_lft forever preferred_lft forever
$ ip addr show veth1
29: veth1@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
link/ether b6:41:52:5f:ef:eb brd ff:ff:ff:ff:ff:ff link-netns net1
inet6 fe80::b4fa:8f8c:5976:59c9/64 scope link
valid_lft forever preferred_lft forever
请注意,默认命名空间中的虚拟以太网设备没有 IP 地址——它们不需要 IP 地址,因为我们通过网桥路由到达主机。如果需要,我们可以为它们提供相应设备的 IP 地址veth
,以便直接路由到它们,无需桥接。以下是创建的命名空间所看到的内容:
# ip netns exec net0 ip addr show veth
26: veth@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 46:11:7c:77:fc:01 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.0.2/24 brd 10.0.0.255 scope global veth
valid_lft forever preferred_lft forever
inet6 fe80::4411:7cff:fe77:fc01/64 scope link
valid_lft forever preferred_lft forever
# ip netns exec net1 ip addr show veth
28: veth@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 12:bc:a0:99:8d:43 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.0.3/24 brd 10.0.0.255 scope global veth
valid_lft forever preferred_lft forever
inet6 fe80::10bc:a0ff:fe99:8d43/64 scope link
valid_lft forever preferred_lft forever
现在让我们尝试 ping。我们可以使用ip neighbour
和 桥来监控 ARP 缓存,tcpdump
让我们确信事情正在按预期工作:
$ ip neigh
$ ping -c 2 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.124 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.059 ms
--- 10.0.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 58ms
rtt min/avg/max/mdev = 0.059/0.091/0.124/0.033 ms
$ ip neigh
10.0.0.2 dev br0 lladdr 46:11:7c:77:fc:01 REACHABLE
从另一个终端,在 ping 之前启动:
# tcpdump -i br0
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on br0, link-type EN10MB (Ethernet), capture size 262144 bytes
00:54:49.536867 ARP, Request who-has 10.0.0.2 tell 10.0.0.1, length 28
00:54:49.536908 ARP, Reply 10.0.0.2 is-at 46:11:7c:77:fc:01 (oui Unknown), length 28
00:54:49.536911 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 9342, seq 1, length 64
00:54:49.536937 IP 10.0.0.2 > 10.0.0.1: ICMP echo reply, id 9342, seq 1, length 64
00:54:50.594136 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 9342, seq 2, length 64
00:54:50.594174 IP 10.0.0.2 > 10.0.0.1: ICMP echo reply, id 9342, seq 2, length 64
可以使用该ip netns exec
命令在每个网络命名空间内重复此操作,得到相同的结果。最后,我们可以使用一个简单的程序来测试跨两个命名空间的多播流量,该socat
程序侦听一个命名空间中的多播地址,并在另一个命名空间中发送多播流量:
# ip netns exec net0 socat PIPE \
> UDP-RECVFROM:9000,bind=239.0.0.1,ip-add-membership=239.0.0.1:veth &
[1] 9474
# echo ECHO | ip netns exec net1 socat STDIO \
> UDP-DATAGRAM:239.0.0.1:9000,bind=10.0.0.3:9000
ECHO
[1]+ Done
和地址socat
PIPE
类型UDP-RECVFROM
等待在端口 9000 上接收 UDP 数据报,将其写入无名管道,从无名管道中读回,然后将其作为单播 UDP 数据报在端口 9000 上发送回源 IP 地址。和地址STDIO
类型UDP-DATAGRAM
从 读取数据stdin
,将其作为多播 UDP 数据报发送,接收单播 UDP 数据报,并将其内容写入stdout
。
从另一个终端,在服务器之前启动:
# tcpdump -i br0
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on br0, link-type EN10MB (Ethernet), capture size 262144 bytes
01:06:04.002116 ARP, Request who-has 10.0.0.3 tell 10.0.0.2, length 28
01:06:04.002129 ARP, Reply 10.0.0.3 is-at 12:bc:a0:99:8d:43 (oui Unknown), length 28
01:06:05.126134 IP 10.0.0.2 > igmp.mcast.net: igmp v3 report, 1 group record(s)
01:06:05.858118 IP 10.0.0.2 > igmp.mcast.net: igmp v3 report, 1 group record(s)
01:06:06.368349 IP 10.0.0.3.9000 > 239.0.0.1.9000: UDP, length 5
01:06:06.368499 IP 10.0.0.2.9000 > 10.0.0.3.9000: UDP, length 5
01:06:06.371106 IP 10.0.0.2 > igmp.mcast.net: igmp v3 report, 1 group record(s)
01:06:06.946105 IP 10.0.0.2 > igmp.mcast.net: igmp v3 report, 1 group record(s)
极好的。