我需要在 Linux 上实现基于 VLAN 的透明 LAN 服务。这意味着我需要获取已配置的 VLAN 并将其直接转发到指定端口(所有广播/多播和单播数据包)。
简单的解决方案是在 VLAN 接口和指定端口之间定义 1 对 1 桥接。此解决方案的缺点是我会知道此隧道上的所有 mac 地址。我正在使用 mac 表有限的嵌入式设备运行,并且希望避免使用我连接的网络的设备污染 mac 表。
我试图找到某种方法来使用 ebtables 来完成这项任务,但似乎 ebtables 的 -o 选项仅在 FORWARD 链上有用,而这恰好是在学习 mac 之后。BROUTING 链是我需要的,但似乎我无法强制数据包从此时点在特定接口上传出。
因此,ebtables 似乎是死路一条。还有其他选择吗?在理想情况下,我更希望拥有基于任何密钥的 TLS 服务,而不仅仅是 VLAN,但目前 VLAN 就可以了。
谢谢,伊利亚。
答案1
更新:添加了一个仍使用桥接器的解决方案。有可能,对于 VLAN 情况无论如何,使用 Linux 桥接器的 VLAN 过滤功能,并完全禁用 MAC 学习。tc
下面可能仍然有用,因为它的通用选择器匹配方式(它可能更容易使用碳使用除 VLAN 之外的其他适当匹配作为选择器,而不是使用没有代码的网桥来处理它)。
每个端口均禁用 MAC 学习的桥接器
可以禁用 MAC 地址学习。使用bridge link
命令。然后可以设置网桥进行 VLAN 过滤(也可以使用bridge vlan
):它不需要任何MAC地址,所有转发都将基于配置的VLAN设置完成。
持续学习或停止学习
控制给定端口是否从接收的流量中学习 MAC 地址。如果学习关闭,网桥将最终泛洪任何没有 FDB 条目的流量。默认情况下,此标志处于打开状态。
learning_sync 开启或 learning_sync 关闭
控制给定端口是否将在设备端口上学习到的 MAC 地址同步到桥接 FDB。
例如,让我们看一下一个系统,其中接口 eth0 为中继,带有标记帧,而接口 eth1、eth2 和 eth3 分别用于 vlan id 10、20 和 30,未标记。这将通过以下方式完成:
ip link add name br0 type bridge vlan_filtering 1
#remove implicit bridge's self port br0 from any interaction.
# Might have to not be done if using an IP on the bridge
# but more configuration might then be needed anyway.
bridge vlan del vid 1 dev br0 self
bridge link set dev br0 learning off learning_sync off self
for $nic in eth0 eth1 eth2 eth3; do
ip link set dev $nic master br0
bridge link set dev $nic learning off learning_sync off
bridge vlan del vid 1 dev $nic
done
ip link set br0 up
bridge vlan add 10 dev eth0
bridge vlan add 20 dev eth0
bridge vlan add 30 dev eth0
bridge vlan add vid 10 pvid 10 untagged dev eth1
bridge vlan add vid 20 pvid 20 untagged dev eth2
bridge vlan add vid 30 pvid 30 untagged dev eth3
要测试这个不同的设置是如何运行的,只需在最后的安装脚本中替换以下几行(使用碳方法在答案的下一部分中描述):
ip netns exec fakebridge tc qdisc add dev trunk0 ingress
for vlan in 10 20 30; do
ip netns exec fakebridge tc qdisc add dev vlan$vlan ingress
ip netns exec fakebridge tc filter add dev vlan$vlan parent ffff: matchall action vlan push id $vlan action mirred egress redirect dev trunk0
ip netns exec fakebridge tc filter add dev trunk0 parent ffff: basic match "meta(vlan mask 0xfff eq $vlan)" action vlan pop action mirred egress redirect dev vlan$vlan
done
用这些来代替(它不再是伪造的桥梁,但无论如何......):
ip -n fakebridge link add name br0 type bridge vlan_filtering 1
ip netns exec fakebridge bridge vlan del vid 1 dev br0 self #remove implicit bridge's self port br0 from any interaction
ip -n fakebridge link set dev trunk0 master br0
ip netns exec fakebridge bridge vlan del vid 1 dev trunk0
ip netns exec fakebridge bridge link set dev trunk0 learning off learning_sync off
for vlan in 10 20 30; do
ip -n fakebridge link set dev vlan$vlan master br0
ip netns exec fakebridge bridge link set dev vlan$vlan learning off learning_sync off
ip netns exec fakebridge bridge vlan add vid $vlan dev trunk0
ip netns exec fakebridge bridge vlan del vid 1 dev vlan$vlan
ip netns exec fakebridge bridge vlan add vid $vlan pvid $vlan untagged dev vlan$vlan
done
ip -n fakebridge link set br0 up
也可以完全不使用网桥,而使用 VLAN ID 进行操作,使用...
碳(交通管制)
碳能够直接使用tc 虚拟局域网:
描述
vlan 操作允许对数据包执行 802.1Q 封装或解封装,反映在操作模式 POP、PUSH 和 MODIFY 中。POP 模式很简单,因为无需进一步的信息即可丢弃最外层的 VLAN 封装。PUSH 和 MODIFY 模式至少需要 VLANID,并允许随意选择要使用的 VLANPROTO。
以及其他碳特征:
- 匹配(在旧内核中可以替换为
u32 match u32 0 0
)无条件匹配数据包, - 基本的+匹配匹配 vlan id 等元信息(此 SF Q/A 有帮助:tc u32 — 如何匹配最新内核中的 L2 协议?),
- 米尔德实际上是在无需桥接或路由的情况下在接口之间移动数据包。
和通常的管道(有队列规定, 附筛选和行动),可以将数据包从一个接口移动到另一个接口,同时封装或解封装 802.1Q VLAN ID。系统不会桥接或路由这些数据包。系统不必记住 MAC 地址或操纵 IP,它对数据包和协议的认识将仅限于对 所做的操作tc
。
请注意,这只是一个概念验证。当然,真正的系统仍然必须使用 IP 进行通信,注意不要干扰这些设置。考虑到以下因素,正确实施此生产可能会带来无法预见的额外困难碳是一个复杂的工具。可能还有其他更好的方法碳更通用地处理事情(思考tc 流量使用 VLAN ID 作为密钥映射到 classid,这可以更通用地使用,或者可以使用 VLAN 之外的其他东西作为密钥,只要有一种方法可以进行封装/解封装。
例如,让我们看一下一个系统,其中接口 eth0 为中继,带有标记帧,而 eth1、eth2 和 eth3 分别用于 vlan id 10、20 和 30,未标记。允许标记端与正确的未标记端进行通信,反之亦然,如下所示:
tc qdisc add dev eth0 handle ffff: ingress
tc qdisc add dev eth1 handle ffff: ingress
tc qdisc add dev eth2 handle ffff: ingress
tc qdisc add dev eth3 handle ffff: ingress
tc filter add dev eth0 parent ffff: basic match "meta(vlan mask 0xfff eq 10)" action vlan pop action mirred egress redirect dev eth1
tc filter add dev eth0 parent ffff: basic match "meta(vlan mask 0xfff eq 20)" action vlan pop action mirred egress redirect dev eth2
tc filter add dev eth0 parent ffff: basic match "meta(vlan mask 0xfff eq 30)" action vlan pop action mirred egress redirect dev eth3
tc filter add dev eth1 parent ffff: matchall action vlan push id 10 action mirred egress redirect dev eth0
tc filter add dev eth2 parent ffff: matchall action vlan push id 20 action mirred egress redirect dev eth0
tc filter add dev eth3 parent ffff: matchall action vlan push id 30 action mirred egress redirect dev eth0
实际接口必须置于混杂模式才能真正重定向流量,这看起来合乎逻辑,但内核 5.0.x 不需要这样做,韦特无论如何,在测试时都要使用接口。
模型使用网际协议网络对于网络命名空间
我做了一些实验,使用网络命名空间实现了一个带有一个带标签的中继接口和几个不带标签的 VLAN 接口的假网桥。每个“主机”都有自己的命名空间,并使用网络元素链接到其他主机,这些网络元素本身是用其他网络命名空间,其中包括一个桥接器。模拟嵌入式设备实际功能的系统将被称为假桥,因为它看起来类似于 VLAN 感知桥。
标记 未标记 _______ .|host10b| +-------+ 。 ======= +------+ | |....vlan10....|host10| | |.......树干......|假的 | ====== |路由器|....| |....vlan20....|host20| | | (VLAN 10+20+30)|桥接| ====== +------+ | |....vlan30....|host30| +-------+ ------
因此 1+1+4 = 6 个主机,1 + 3 = 4 个网络,总共 10 个命名空间。
一旦运行下面的脚本(以 root 身份),就可以使用以下命令进行测试和观察:
术语1:
ip netns exec fakebridge tcpdump -l -n -s0 -e -p -i trunk0
期限2:
ip netns exec host10 ping -c1 198.51.100.20
例如给予:
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on trunk0, link-type EN10MB (Ethernet), capture size 262144 bytes
00:27:56.036743 c2:e8:f4:79:28:96 > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), length 46: vlan 10, p 0, ethertype ARP, Request who-has 192.0.2.110 tell 192.0.2.10, length 28
00:27:56.036777 16:51:fa:18:21:b0 > c2:e8:f4:79:28:96, ethertype 802.1Q (0x8100), length 46: vlan 10, p 0, ethertype ARP, Reply 192.0.2.110 is-at 16:51:fa:18:21:b0, length 28
00:27:56.036794 c2:e8:f4:79:28:96 > 16:51:fa:18:21:b0, ethertype 802.1Q (0x8100), length 102: vlan 10, p 0, ethertype IPv4, 192.0.2.10 > 198.51.100.20: ICMP echo request, id 13483, seq 1, length 64
00:27:56.036807 16:51:fa:18:21:b0 > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), length 46: vlan 20, p 0, ethertype ARP, Request who-has 198.51.100.20 tell 198.51.100.120, length 28
00:27:56.036832 b6:1d:bc:33:87:98 > 16:51:fa:18:21:b0, ethertype 802.1Q (0x8100), length 46: vlan 20, p 0, ethertype ARP, Reply 198.51.100.20 is-at b6:1d:bc:33:87:98, length 28
00:27:56.036841 16:51:fa:18:21:b0 > b6:1d:bc:33:87:98, ethertype 802.1Q (0x8100), length 102: vlan 20, p 0, ethertype IPv4, 192.0.2.10 > 198.51.100.20: ICMP echo request, id 13483, seq 1, length 64
00:27:56.036860 b6:1d:bc:33:87:98 > 16:51:fa:18:21:b0, ethertype 802.1Q (0x8100), length 102: vlan 20, p 0, ethertype IPv4, 198.51.100.20 > 192.0.2.10: ICMP echo reply, id 13483, seq 1, length 64
00:27:56.036867 16:51:fa:18:21:b0 > c2:e8:f4:79:28:96, ethertype 802.1Q (0x8100), length 102: vlan 10, p 0, ethertype IPv4, 198.51.100.20 > 192.0.2.10: ICMP echo reply, id 13483, seq 1, length 64
00:28:01.043203 16:51:fa:18:21:b0 > c2:e8:f4:79:28:96, ethertype 802.1Q (0x8100), length 46: vlan 10, p 0, ethertype ARP, Request who-has 192.0.2.10 tell 192.0.2.110, length 28
00:28:01.043246 b6:1d:bc:33:87:98 > 16:51:fa:18:21:b0, ethertype 802.1Q (0x8100), length 46: vlan 20, p 0, ethertype ARP, Request who-has 198.51.100.120 tell 198.51.100.20, length 28
00:28:01.043287 c2:e8:f4:79:28:96 > 16:51:fa:18:21:b0, ethertype 802.1Q (0x8100), length 46: vlan 10, p 0, ethertype ARP, Reply 192.0.2.10 is-at c2:e8:f4:79:28:96, length 28
00:28:01.043284 16:51:fa:18:21:b0 > b6:1d:bc:33:87:98, ethertype 802.1Q (0x8100), length 46: vlan 20, p 0, ethertype ARP, Reply 198.51.100.120 is-at 16:51:fa:18:21:b0, length 28
以 root 身份运行的安装脚本。它使用以下方式创建各种网络命名空间ip netns
,填充所需的网络链接(网桥和韦特)设置碳筛选假桥,最后配置各个主机的 IP,以便进行实验。假桥保持无 IP 或桥接。没有 MAC 表可以填充:ip neigh
或者bridge fdb
不会显示任何与流量相关的内容,因为没有 IP 就没有 ARP,没有桥接就没有 MAC 学习。
#!/bin/sh
if ip netns id | grep -qv '^ *$' ; then
printf 'ERROR: leave netns "%s" first\n' $(ip netns id) >&2
exit 1
fi
hosts='router fakebridge host10 host10b host20 host30'
nets='trunk vlan10 vlan20 vlan30'
for ns in $hosts $nets; do
ip netns del $ns 2>/dev/null || :
ip netns add $ns
ip netns exec $ns sysctl -q -w net.ipv6.conf.default.disable_ipv6=1
ip netns exec $ns sysctl -q -w net.ipv4.icmp_echo_ignore_broadcasts=0
done
for ns in $hosts; do
ip -n $ns link set lo up
done
bmac=1
for ns in $nets; do
ip -n $ns link add bridge0 address 02:00:00:00:00:$(printf '%02d' $bmac) type bridge
ip -n $ns link set bridge0 up
bmac=$(($bmac+1))
done
link_ns () {
ip -n $1 link add name "$3" type veth peer netns $2 name "$4"
ip -n $1 link set dev "$3" up
ip -n $2 link set dev "$4" up
if printf '%s\n' "$nets" | grep -q -w "$1"; then
ip -n "$1" link set dev "$3" master bridge0
fi
if printf '%s\n' "$nets" | grep -q -w "$2"; then
ip -n "$2" link set dev "$4" master bridge0
fi
}
link_ns trunk fakebridge fakebridge trunk0
link_ns vlan10 fakebridge fakebridge vlan10
link_ns vlan20 fakebridge fakebridge vlan20
link_ns vlan30 fakebridge fakebridge vlan30
link_ns trunk router router trunk0
link_ns vlan10 host10 host10 eth0
link_ns vlan10 host10b host10b eth0
link_ns vlan20 host20 host20 eth0
link_ns vlan30 host30 host30 eth0
ip netns exec fakebridge tc qdisc add dev trunk0 ingress
for vlan in 10 20 30; do
ip netns exec fakebridge tc qdisc add dev vlan$vlan ingress
ip netns exec fakebridge tc filter add dev vlan$vlan parent ffff: matchall action vlan push id $vlan action mirred egress redirect dev trunk0
ip netns exec fakebridge tc filter add dev trunk0 parent ffff: basic match "meta(vlan mask 0xfff eq $vlan)" action vlan pop action mirred egress redirect dev vlan$vlan
done
for vlan in 10 20 30; do
ip -n router link add link trunk0 name trunk.$vlan type vlan id $vlan
ip -n router link set dev trunk.$vlan up
ip netns exec router sysctl -q -w net.ipv4.conf.trunk/$vlan.forwarding=1
done
ip -n router address add 192.0.2.110/24 dev trunk.10
ip -n router address add 198.51.100.120/24 dev trunk.20
ip -n router address add 203.0.113.130/24 dev trunk.30
ip -n host10 address add 192.0.2.10/24 dev eth0
ip -n host10b address add 192.0.2.11/24 dev eth0
ip -n host20 address add 198.51.100.20/24 dev eth0
ip -n host30 address add 203.0.113.30/24 dev eth0
ip -n host10 route add default via 192.0.2.110
ip -n host10b route add default via 192.0.2.110
ip -n host20 route add default via 198.51.100.120
ip -n host30 route add default via 203.0.113.130