simple-l2-switch
控制器侧 L2 学习交换机。l2_forward 表命中时数据面按目的 MAC 转发;miss 时 BMv2 把帧上送给控制器,控制器 学 srcAddr → ingress_port,并通过 PacketOut 向其它前面板端口 flood 该帧。
GitHub 源码: examples/simple-l2-switch/(Java + P4 + Gradle 构建)
示例展示了什么
- 以主控连接并推送流水线(
P4Switch.connectAsPrimary+bindPipeline)。 - 注册
PacketIn回调(sw.onPacketIn)、读取controller_packet_metadata(packet.metadataInt("ingress_port"))。 - 在回调里写入表条目(
TableEntry.in("…").match(…).action(…).build()+sw.insert)。 - 发送
PacketOut(PacketOut.builder().payload(…).metadata("egress_port", …).build()+sw.send)。 - try-with-resources 生命周期(
P4Switch implements AutoCloseable)。
本地运行
完整前置与 docker run 行见 示例的 README;快速开始 端到端给出相同命令。简版:
bash
# After starting BMv2 in another terminal (see the README):
cd examples
./gradlew :simple-l2-switch:run可选首参数覆盖: --args="my-bmv2-host:50051"。
期望输出
[L2] connected as primary on 127.0.0.1:50051, pipeline pushed
[L2] inject src=aa:00:00:00:00:01 dst=ff:ff:ff:ff:ff:ff via simulated ingress 1
[L2] PacketIn src=AA:00:00:00:00:01 dst=FF:FF:FF:FF:FF:FF ingress=1
[L2] LEARN AA:00:00:00:00:01 → port 1 (entry installed)
[L2] inject src=bb:00:00:00:00:02 dst=ff:ff:ff:ff:ff:ff via simulated ingress 2
[L2] PacketIn src=BB:00:00:00:00:02 dst=FF:FF:FF:FF:FF:FF ingress=2
[L2] LEARN BB:00:00:00:00:02 → port 2 (entry installed)
[L2] learned table: {AA:00:00:00:00:01=1, BB:00:00:00:00:02=2}两行 LEARN 确认 jp4 已写入转发条目;后续发往这两个 MAC 的流量会在数据面短路通过(无控制器跳)。两个 demo 帧都以广播 MAC FF:FF:FF:FF:FF:FF 为目的,所以都会 miss l2_forward 并触发学习路径。
可以尝试
- 把
sw.insert(e)换成sw.modify(e),观察设备对"键已存在 vs 不存在"的响应差异。 - 订阅
sw.packetInStream()而非sw.onPacketIn(...),用Flow.Subscriber消费报文 —— 两种风格从同一条底层流扇出。network-monitor 示例 端到端展示Flow.Subscriber形态。 - 运行中用
sw.delete(e)删一条学过的条目,观察下一报文重新触发学习循环。
自流量说明
示例用 sw.send(...) 注入 demo 帧,这样在单机上无需 mininet / tcpreplay / 真接口就能看到学习循环。simple_l2.p4 程序把控制器传入的 packet_out.egress_port 当作 该回环 demo 的模拟入口端口;生产控制器的 PacketOut 用该字段的常规出口含义,真实入口流量来自网络。Java 控制器代码不变。