跳转至

双栈

一台交换机上的两台主机,各自既配 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.ipHost.ip6 同时设置。编排器检测到这一点后,会在拉起 接口之前调用 enable_ipv6(ns, iface)(同时 accept_ra=0autoconf=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 走完全相同的路径。

运行

sudo p4net examples/dual_stack/topology.py
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=0autoconf=0disable_ipv6=0 一同 写入,使得内核不会偷偷地从 Router Advertisement 自动配置 额外地址(这里没有 RA,但行为仍然要可预期)。
  • 静态 NDsetup(net) 中注入——关闭 accept_ra 后, IPv6 邻居发现仍然能工作,但每次冷启动 ping 都要走 ND 解析 会拖慢测量。预先注入条目让延迟测量结果更干净。

可尝试的变体

  • 把其中一台主机的 ip6 参数去掉,确认 pingall6 矩阵中确实 排除了它(按 primary_ip6 过滤)。
  • 在显式调用 enable_ipv6(...) 时把 accept_ra=True,观察会 出现哪些地址(需要绕过编排器)。
  • 加一个 loss_pct=10.0 的链路参数,观察 v4 与 v6 ping 得到 相同的丢包率(qdisc 对 L3 无感)。