双栈¶
一台交换机上的两台主机,各自既配 IPv4 /24 也配 IPv6 /64。
流水线与快速上手相同——L2 端口翻转一视同仁
对待 v4 与 v6。本例的看点是地址管理。
你将看到什么¶
pingall(IPv4)与 pingall6(IPv6)都成功。主机接口上只有
我们显式声明的地址——没有 fe80:: link-local 噪声,也没有
SLAAC 派生的地址。
拓扑¶
examples/dual_stack/topology.py:
"""Two hosts plus one switch carrying both IPv4 and IPv6.
The pipeline is L3-agnostic (it just swaps ports 1 and 2), so both v4 and
v6 traverse identically. ``setup(net)`` seeds static ARP and ND so the
hosts don't have to resolve neighbours at run time.
Run with:
sudo p4net examples/dual_stack/topology.py
Then in the shell:
pingall
h1 ping h2
h1 ping6 h2
"""
from __future__ import annotations
from pathlib import Path
from p4net import Network
from p4net.topo import Topology
HERE = Path(__file__).resolve().parent
topology = Topology()
h1 = topology.add_host(
"h1",
ip="10.0.0.1/24",
mac="00:00:00:00:00:01",
ip6="fd00::1/64",
)
h2 = topology.add_host(
"h2",
ip="10.0.0.2/24",
mac="00:00:00:00:00:02",
ip6="fd00::2/64",
)
s1 = topology.add_switch("s1", p4_src=HERE / "dual_stack.p4")
topology.add_link(h1, s1, port_b=1)
topology.add_link(h2, s1, port_b=2)
def setup(net: Network) -> None:
"""Pre-seed static ARP and ND so ICMP unicast doesn't have to resolve."""
h1 = net.host("h1")
h2 = net.host("h2")
h1.exec(
[
"ip",
"neigh",
"replace",
"10.0.0.2",
"lladdr",
"00:00:00:00:00:02",
"dev",
"h1-eth0",
"nud",
"permanent",
]
)
h2.exec(
[
"ip",
"neigh",
"replace",
"10.0.0.1",
"lladdr",
"00:00:00:00:00:01",
"dev",
"h2-eth0",
"nud",
"permanent",
]
)
h1.exec(
[
"ip",
"-6",
"neigh",
"replace",
"fd00::2",
"lladdr",
"00:00:00:00:00:02",
"dev",
"h1-eth0",
"nud",
"permanent",
]
)
h2.exec(
[
"ip",
"-6",
"neigh",
"replace",
"fd00::1",
"lladdr",
"00:00:00:00:00:01",
"dev",
"h2-eth0",
"nud",
"permanent",
]
)
if __name__ == "__main__":
from p4net.cli.main import main
raise SystemExit(main([__file__]))
Host.ip 与 Host.ip6 同时设置。编排器检测到这一点后,会在拉起
接口之前调用 enable_ipv6(ns, iface)(同时 accept_ra=0、
autoconf=0),然后把两个地址都赋上。
P4 程序¶
examples/dual_stack/dual_stack.p4:
#include <core.p4>
#include <v1model.p4>
header ethernet_t {
bit<48> dstAddr;
bit<48> srcAddr;
bit<16> etherType;
}
struct headers {
ethernet_t ethernet;
}
struct metadata {}
parser MyParser(packet_in pkt, out headers hdr, inout metadata meta,
inout standard_metadata_t std) {
state start {
pkt.extract(hdr.ethernet);
transition accept;
}
}
control MyVerifyChecksum(inout headers hdr, inout metadata meta) { apply {} }
control MyIngress(inout headers hdr, inout metadata meta,
inout standard_metadata_t std) {
apply {
if (std.ingress_port == 1) {
std.egress_spec = 2;
} else if (std.ingress_port == 2) {
std.egress_spec = 1;
} else {
mark_to_drop(std);
}
}
}
control MyEgress(inout headers hdr, inout metadata meta,
inout standard_metadata_t std) { apply {} }
control MyComputeChecksum(inout headers hdr, inout metadata meta) { apply {} }
control MyDeparser(packet_out pkt, in headers hdr) {
apply {
pkt.emit(hdr.ethernet);
}
}
V1Switch(MyParser(), MyVerifyChecksum(), MyIngress(), MyEgress(),
MyComputeChecksum(), MyDeparser()) main;
流水线对 L3 无感——只做端口翻转。v4 与 v6 走完全相同的路径。
运行¶
p4net> hosts
name primary_ip primary_ip6 interfaces
h1 10.0.0.1/24 fd00::1/64 h1-eth0
h2 10.0.0.2/24 fd00::2/64 h2-eth0
p4net> h1 cmd ip -6 addr show dev h1-eth0
3: h1-eth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
inet6 fd00::1/64 scope global
valid_lft forever preferred_lft forever
p4net> pingall
2/2 succeeded
p4net> pingall6
2/2 succeeded
注意:只有 fd00::1/64,没有 fe80:: link-local——sysctl 门控
正在生效。
关键设计点¶
accept_ra=0与autoconf=0与disable_ipv6=0一同 写入,使得内核不会偷偷地从 Router Advertisement 自动配置 额外地址(这里没有 RA,但行为仍然要可预期)。- 静态 ND 在
setup(net)中注入——关闭accept_ra后, IPv6 邻居发现仍然能工作,但每次冷启动 ping 都要走 ND 解析 会拖慢测量。预先注入条目让延迟测量结果更干净。
可尝试的变体¶
- 把其中一台主机的
ip6参数去掉,确认pingall6矩阵中确实 排除了它(按primary_ip6过滤)。 - 在显式调用
enable_ipv6(...)时把accept_ra=True,观察会 出现哪些地址(需要绕过编排器)。 - 加一个
loss_pct=10.0的链路参数,观察 v4 与 v6 ping 得到 相同的丢包率(qdisc 对 L3 无感)。