Skip to content

simple-l2-switch ​

A controller-side L2 learning switch. The data plane forwards by destination MAC when a hit exists in l2_forward; on miss, BMv2 sends the packet up to the controller, which learns srcAddr → ingress_port and floods the packet to all other front-panel ports via PacketOut.

Source on GitHub: examples/simple-l2-switch/ (Java + P4 + Gradle build)

What this example demonstrates ​

  • Connecting as primary and pushing a pipeline (P4Switch.connectAsPrimary + bindPipeline).
  • Registering a PacketIn callback (sw.onPacketIn) and reading the controller_packet_metadata (packet.metadataInt("ingress_port")).
  • Writing table entries from the callback (TableEntry.in("…").match(…).action(…).build() + sw.insert).
  • Sending PacketOut (PacketOut.builder().payload(…).metadata("egress_port", …).build() + sw.send).
  • Try-with-resources lifecycle (P4Switch implements AutoCloseable).

Running locally ​

The full prerequisites and docker run line are in the example's README; the Quickstart carries the same commands end-to-end. Briefly:

bash
# After starting BMv2 in another terminal (see the README):
cd examples
./gradlew :simple-l2-switch:run

Optional first-arg override: --args="my-bmv2-host:50051".

Expected output ​

[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}

The two LEARN lines confirm jp4 wrote forwarding entries; subsequent traffic to either MAC would short-circuit through the data plane (no controller hop). Both demo frames target the broadcast MAC FF:FF:FF:FF:FF:FF so each one misses l2_forward and triggers the learning path.

Things to try ​

  • Replace sw.insert(e) with sw.modify(e) and observe the device's response when the entry already exists vs not.
  • Subscribe to sw.packetInStream() instead of sw.onPacketIn(...) and consume packets via a Flow.Subscriber — both styles fan out from the same underlying stream. The network-monitor example shows the Flow.Subscriber shape end-to-end.
  • Remove a learned entry with sw.delete(e) mid-run and watch the next packet to that MAC trigger another learn cycle.

Self-traffic note ​

The example uses sw.send(...) to inject demo frames so that you can see the learn cycle on a single machine without mininet / tcpreplay / real interfaces. The simple_l2.p4 program treats the controller-supplied packet_out.egress_port as the simulated ingress port for this loopback demo; a production controller's PacketOut would use that field with its conventional egress meaning, and real ingress traffic would come from the network. The Java controller code stays the same.

See also ​

Released under the Apache License 2.0.