Quick start (port swap)¶
Two hosts on a single switch with a static port-swap pipeline. No runtime table programming. The "hello world" of p4net.
What you'll see¶
A successful pingall between two hosts whose dataplane is a 30-line
P4 program that swaps ports 1 ↔ 2 unconditionally.
Topology¶
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()
The interesting bits:
setup(net)is the hook thep4netconsole script calls between bring-up and shell. Static ARP is seeded here so the first ICMP doesn't have to resolve.- The same file works under
python quick_start.py(theif __name__ == "__main__"block) or underp4net quick_start.py(the module-leveltopologyandsetup).
P4 program¶
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;
The ingress control sets std.egress_spec based on ingress_port —
no tables, no runtime control plane needed.
Run it¶
Then in the 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
What's interesting¶
- It's the smallest possible working program. If
pingallsucceeds here, the rest of the toolchain (p4c, BMv2, namespaces, veth pairs, P4Runtime) is operational. - The dataplane has no notion of L3 — no IPv4 header parsing, no
ARP. Static ARP in
setup(net)is what makes the L3 ping work.
Variations to try¶
- Add a third host on port 3. Without table programming, packets to port 3 hit the implicit drop (since the port-swap covers only 1 ↔ 2).
- Replace the conditional with a single
mark_to_drop(std)and watchpingallproduce allXcells. - Set a
Link(..., loss_pct=20.0)and observe the success rate drop inpingall 10 1.