Simulate an OpenFlow learning controller
Handle simulated Packet-In events, install and inspect learning-switch flows, then prove an IoT-isolation policy with priorities, counters, receiver results, and cleanup.

Cloud Clara: I want you to connect controller intent to table state, counters, receiver behavior, protected paths, and cleanup.
Predict the reading, then compare it with the measurement.
Python 3 in your browser (JupyterLite)
Python · no installOpen the notebook in your browser and run each Python cell; no install or account is needed.
Three ways to run: use JupyterLite here with no install; run main.py locally from the downloadable lab folder; or open the same notebook in Google Colab.
Steps
Step 1
- Do
- In the JupyterLite notebook, run the Step 1 cell to inspect the simulated two-switch topology and initial table-miss rule.
- You will see
- Input: execute notebook step 1 with the record’s unchanged fixture, seed, and parameters. Observed: Simulated OpenFlow 1.3 topology Observed: sensor-1 --(port 1) s1-edge (port 3)-- s2-services -- telemetry Observed: laptop-1 --(port 2) s1-edge -- admin
- Why it matters
- Expected: STEP 1 topology switches=2 hosts=4 table-miss=CONTROLLER from this deterministic notebook step. Interpretation: The explicit table-miss establishes why an unknown packet reaches control logic instead of disappearing silently. Action: Run python3 main.py --step 1 to inspect the simulated two-switch topology and initial table-miss rule. Boundary: this local fixture is not a live device, network, or site measurement.

Step 1 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 2
- Do
- In the JupyterLite notebook, run the Step 2 cell to send the first frame to an unknown destination.
- You will see
- Input: execute notebook step 2 with the record’s unchanged fixture, seed, and parameters. Observed: First packet: unknown destination Observed: RX s1-edge port=1 src=02:00:00:00:00:01 dst=02:00:00:00:00:02 Observed: Table lookup: priority=0 match=* action=CONTROLLER
- Why it matters
- Expected: STEP 2 packet-in learned=02:00:00:00:00:01@1 action=FLOOD from this deterministic notebook step. Interpretation: The event shows which state can be learned safely from ingress and why no destination-specific rule exists yet. Action: Run python3 main.py --step 2 to send the first frame to an unknown destination. Boundary: this local fixture is not a live device, network, or site measurement.

Step 2 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 3
- Do
- In the JupyterLite notebook, run the Step 3 cell to deliver the reply and install forward and reverse learned flows.
- You will see
- Input: execute notebook step 3 with the record’s unchanged fixture, seed, and parameters. Observed: Reply packet: destination is now known Observed: RX s1-edge port=2 src=02:00:00:00:00:02 dst=02:00:00:00:00:01 Observed: Controller learns 02:00:00:00:00:02 -> port 2
- Why it matters
- Expected: STEP 3 learning-switch macs=2 installed=2 reply=OUTPUT:1 from this deterministic notebook step. Interpretation: Controller intent becomes reviewable switch state only after the matches, actions, and acknowledgement are visible. Action: Run python3 main.py --step 3 to deliver the reply and install forward and reverse learned flows. Boundary: this local fixture is not a live device, network, or site measurement.

Step 3 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 4
- Do
- In the JupyterLite notebook, run the Step 4 cell to print the resulting s1-edge flow table.
- You will see
- Input: execute notebook step 4 with the record’s unchanged fixture, seed, and parameters. Observed: Flow table: s1-edge Observed: prio match action packets Observed: 100 in_port=2,dst=02:00:00:00:00:01 OUTPUT:1 0
- Why it matters
- Expected: STEP 4 flow-table entries=3 learned=2 table-miss=1 from this deterministic notebook step. Interpretation: A table dump proves final ordering and preserves the fallback path for addresses the controller has not learned. Action: Run python3 main.py --step 4 to print the resulting s1-edge flow table. Boundary: this local fixture is not a live device, network, or site measurement.

Step 4 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 5
- Do
- In the JupyterLite notebook, run the Step 5 cell to repeat the forward packet after its rule is installed.
- You will see
- Input: execute notebook step 5 with the record’s unchanged fixture, seed, and parameters. Observed: Repeat packet: data-plane table hit Observed: RX s1-edge port=1 dst=02:00:00:00:00:02 Observed: Matched priority=100 match=in_port=1,dst=02:00:00:00:00:02
- Why it matters
- Expected: STEP 5 data-plane-hit action=OUTPUT:2 packets=1 packet-in=NO from this deterministic notebook step. Interpretation: The match, counter movement, controller absence, and receiver result together prove data-plane handling. Action: Run python3 main.py --step 5 to repeat the forward packet after its rule is installed. Boundary: this local fixture is not a live device, network, or site measurement.

Step 5 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 6
- Do
- In the JupyterLite notebook, run the Step 6 cell to install a higher-priority IoT-isolation policy.
- You will see
- Input: execute notebook step 6 with the record’s unchanged fixture, seed, and parameters. Observed: FLOW_MOD priority=300 match=src_group=iot,dst_group=admin action=DROP Observed: FLOW_MOD priority=200 match=src_group=iot,dst_group=telemetry action=OUTPUT:3 Observed: Policy rule: IoT devices may send telemetry but may not initiate admin traffic.
- Why it matters
- Expected: STEP 6 isolation-policy installed=2 deny-priority=300 allow-priority=200 from this deterministic notebook step. Interpretation: Narrow policy matches and explicit priorities keep a broad learning rule from bypassing the protected admin boundary. Action: Run python3 main.py --step 6 to install a higher-priority IoT-isolation policy. Boundary: this local fixture is not a live device, network, or site measurement.

Step 6 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 7
- Do
- In the JupyterLite notebook, run the Step 7 cell to test one allowed telemetry packet and one denied admin packet.
- You will see
- Input: execute notebook step 7 with the record’s unchanged fixture, seed, and parameters. Observed: packet A src_group=iot dst_group=telemetry Observed: packet A matched priority=200 action=OUTPUT:3 receiver=telemetry Observed: packet B src_group=iot dst_group=admin
- Why it matters
- Expected: STEP 7 policy-test telemetry=ALLOW admin=DENY counters=1,1 from this deterministic notebook step. Interpretation: Both positive and protected-path tests are needed to prove isolation without breaking the permitted IoT service. Action: Run python3 main.py --step 7 to test one allowed telemetry packet and one denied admin packet. Boundary: this local fixture is not a live device, network, or site measurement.

Step 7 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 8
- Do
- In the JupyterLite notebook, run the Step 8 cell to remove the temporary isolation rules and close the proof record.
- You will see
- Input: execute notebook step 8 with the record’s unchanged fixture, seed, and parameters. Observed: Before cleanup: entries=5 policy_entries=2 learned_entries=2 table-miss=1 Observed: FLOW_MOD DELETE owner=maintenance-window priorities=200,300 Observed: After cleanup: entries=3 policy_entries=0 learned_entries=2 table-miss=1
- Why it matters
- Expected: STEP 8 cleanup removed=2 remaining=3 proof=COMPLETE from this deterministic notebook step. Interpretation: Explicit cleanup prevents maintenance policy from becoming hidden permanent state and completes the protocol evidence chain. Action: Run python3 main.py --step 8 to remove the temporary isolation rules and close the proof record. Boundary: this local fixture is not a live device, network, or site measurement.

Step 8 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
Chapter checks
These questions refer to the chapter’s examples. Use the return links to review their answers.
During a maintenance window, a controller must steer gateway update downloads without catching telemetry, door-control events, or management access. Which first check keeps the OpenFlow-style rule change traceable?
Return to the chapter’s knowledge checkA controller sends a flow-mod to steer update traffic, and the switch accepts it. What proof is still needed before saying the service behavior worked?
Return to the chapter’s knowledge checkA controller sends two OpenFlow-style changes for a gateway maintenance window: remove an old broad update rule, then install a narrower rule with protected exceptions. Which proof closes the protocol boundary?
Return to the chapter’s knowledge check