跳转至

快速上手(端口翻转)

一台交换机上的两台主机,使用静态端口翻转流水线。无需运行时表项 编程。p4net 的 "Hello World"。

你将看到什么

两台主机间的 pingall 全部成功,数据平面是一个 30 行左右的 P4 程序,在端口 1 ↔ 2 之间无条件互换。

拓扑

examples/quick_start/quick_start.py

"""Minimal p4net quick-start: two hosts plus one BMv2 switch.

The bundled `quick_start.p4` is a port-2-port swap (port 1 <-> port 2),
so no runtime table programming is needed for hosts on opposite ports
to reach each other. Run as root so the orchestrator can create
namespaces and veth pairs:

    sudo python examples/quick_start/quick_start.py
    sudo p4net examples/quick_start/quick_start.py

The first form is a self-contained script. The second uses the `p4net`
console script (installed by `pip install -e .`) to load this file as a
topology module: `topology` and `setup(net)` are the two named hooks
that the console script looks for.
"""

from __future__ import annotations

from pathlib import Path

from p4net import Network
from p4net.network import RunningHost
from p4net.topo import Topology

HERE = Path(__file__).resolve().parent


def _build_topology() -> Topology:
    topo = Topology()
    h1 = topo.add_host("h1", ip="10.0.0.1/24", mac="00:00:00:00:00:01")
    h2 = topo.add_host("h2", ip="10.0.0.2/24", mac="00:00:00:00:00:02")
    s1 = topo.add_switch("s1", p4_src=HERE / "quick_start.p4")
    topo.add_link(h1, s1, port_b=1)
    topo.add_link(h2, s1, port_b=2)
    return topo


# Module-level `topology` for the `p4net` console script.
topology = _build_topology()


def setup(net: Network) -> None:
    """Pre-seed static ARP for both hosts; called by the console script
    after Network.start() and before the shell.

    The same logic also runs inside this script's `__main__` block.
    """
    _add_static_arp(net.host("h1"), "10.0.0.2", "00:00:00:00:00:02")
    _add_static_arp(net.host("h2"), "10.0.0.1", "00:00:00:00:00:01")


def _add_static_arp(host: RunningHost, target_ip: str, target_mac: str) -> None:
    iface = next(iter(host.interfaces))
    host.exec(
        [
            "ip",
            "neigh",
            "replace",
            target_ip,
            "lladdr",
            target_mac,
            "dev",
            iface,
            "nud",
            "permanent",
        ]
    )


def main() -> None:
    """Same flow as `p4net <this file>` but self-contained for direct invocation."""
    with Network(topology) as net:
        setup(net)
        print("hosts:", list(net.hosts))
        print("switches:", list(net.switches))
        print("pingall:", net.pingall())


if __name__ == "__main__":
    main()

值得注意的几个点:

  • setup(net)p4net 控制台脚本在网络拉起后、Shell 进入前 调用的钩子。这里把静态 ARP 注入到主机邻居缓存,避免第一次 ICMP 触发地址解析。
  • 同一份文件既能 python quick_start.py 直接运行(依赖 if __name__ == "__main__" 块),也能 p4net quick_start.py 运行(依赖模块级的 topologysetup)。

P4 程序

examples/quick_start/quick_start.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;

入口控制根据 ingress_port 设置 std.egress_spec——没有表, 也不需要运行时控制平面。

运行

sudo p4net examples/quick_start/quick_start.py

Shell 中:

p4net> hosts
name  primary_ip   primary_ip6  interfaces
h1    10.0.0.1/24  -            h1-eth0
h2    10.0.0.2/24  -            h2-eth0

p4net> pingall
H \ H   h1   h2
   h1    -    1
   h2    1    -
2/2 succeeded

关键设计点

  • 这是最小的可运行程序。如果 pingall 在这里成功,说明工具链 其余部分(p4c、BMv2、命名空间、veth 对、P4Runtime)都正常 工作。
  • 数据平面对 L3 一无所知——既没有 IPv4 报头解析,也没有 ARP 逻辑。setup(net) 注入静态 ARP 使 L3 ping 能够成立。

可尝试的变体

  • 在端口 3 上加一台主机。没有表项编程的话,发往端口 3 的包会 落在隐式 drop 上(端口翻转只覆盖 1 ↔ 2)。
  • 把条件分支换成单纯的 mark_to_drop(std),观察 pingall 全是 X
  • Link(..., loss_pct=20.0),跑 pingall 10 1 观察成功率 下降。