CYC-003 · 2026-06 · network / SDN

Segmenting a campus network with SDN

By · originally a team course project at CUST, Islamabad · source

A student in a computer lab shouldn't be able to reach the administration network. On a flat campus network, nothing stops them. I built a three-tier campus in Mininet with VLANs per department, DHCP and DNS on a central router, and a Ryu OpenFlow controller that drops traffic between departments that shouldn't talk. Then I checked it with flow counters and packet captures, and found where it still falls short.

The problem with a flat network

In a flat network every device shares one broadcast domain. A lab PC can ARP for, sniff, and connect to anything on campus, and one misbehaving device floods everyone.

A capture from an earlier version of the topology, before the VLANs were in place, shows it nicely. Pinging from 192.168.10.10 to 192.168.20.10, the router answered every request with an ICMP Redirect:

192.168.10.10 → 192.168.20.10   ICMP  Echo (ping) request
192.168.10.1  → 192.168.10.10   ICMP  Redirect (Redirect for host)
192.168.20.10 → 192.168.10.10   ICMP  Echo (ping) reply

A router sends a redirect when it would forward a packet back out the same interface it arrived on. In other words, it was telling the host "that machine is on your segment, talk to it directly". Two "different" subnets were sharing one wire. That's exactly what segmentation has to fix.

The design

A classic three-tier layout: one core switch, two distribution switches, one access switch per department, and a Linux router r0 doing inter-VLAN routing.

                 r0   router, one link per VLAN
                 │
                 s1   core   ← ACLs live here
            ┌────┴────┐
            s2        s3     distribution
          ┌─┴─┐     ┌─┴─┐
          s4  s5    s6  s7   access
          │   │     │   │
          h1  h2    h3  h4
          ECE CS    Admin Labs
VLANDepartmentSubnetAddressing
10ECE192.168.10.0/24DHCP .100–.200, also hosts internal DNS
20CS192.168.20.0/24DHCP .100–.200
30Administration192.168.30.0/24Static: server at .10
40Student labs192.168.40.0/24DHCP .100–.200

The policy: student labs can't reach administration, and ECE can't reach CS. Everything else is allowed.

How it works

1. VLANs on Open vSwitch

Mininet builds the topology, then a script tags ports with ovs-vsctl: host-facing ports are access ports in one VLAN, switch-to-switch links are trunks carrying all four.

ovs-vsctl set port <s7 port to h4> tag=40               # access port: h4 is in VLAN 40
ovs-vsctl set port <s1 port to s3> trunks=10,20,30,40   # trunk to distribution

Ports are looked up by which node they connect to rather than hard-coded, because Mininet's interface numbering depends on link order and silently breaks if you add a link.

The router has four separate links into the core switch, each an access port in one VLAN with that VLAN's gateway address (.1). That's simpler to get working than a single trunk with sub-interfaces ("router on a stick"), at the cost of one port per VLAN.

2. A hybrid controller: let OVS switch, only intervene to block

A pure OpenFlow controller has to install a flow for every forwarding decision. Instead, when each switch connects, the Ryu app installs one lowest-priority rule that hands every packet back to Open vSwitch's built-in switching, which already understands VLAN tags and MAC learning:

match = parser.OFPMatch()                              # matches everything
actions = [parser.OFPActionOutput(ofproto.OFPP_NORMAL)] # "switch it normally"
self.add_flow(datapath, 0, match, actions)             # priority 0 = last resort

Security rules then only have to describe what to drop, at a higher priority. The switches are also set to fail-mode secure: if the controller goes away, they stop forwarding instead of falling back to a plain learning switch with no rules. For a security boundary, failing closed is the right default.

3. The ACLs

match = parser.OFPMatch(
    eth_type=0x0800,                       # IPv4
    ipv4_src='192.168.40.0/255.255.255.0', # student labs
    ipv4_dst='192.168.30.0/255.255.255.0') # administration
self.add_flow(datapath, 40000, match, [])  # no actions = drop

The rules are installed only on the core switch, s1. That's enough because every packet between VLANs has to reach the router, and the router only connects through s1. The drop happens before the packet is ever routed.

4. DHCP and DNS from one router

The router runs one dnsmasq instance per VLAN interface, each bound to its own interface with its own address pool, so a DHCP broadcast in VLAN 40 can only ever be answered from the VLAN 40 pool. The ECE instance also serves an internal zone, cust.local, so hosts can resolve names like admin.cust.local without internet access. The setup script checks every lease it gets: if a host ends up with an address from the wrong VLAN's pool, it reports a VLAN leak instead of carrying on.

5. Routing with FRR

The router runs FRRouting's zebra and ospfd inside its Mininet namespace, with all four subnets in OSPF area 0. There's a limit to what that shows with a single router, covered below.

Verifying it

Every host got an address from the right pool: h1 192.168.10.147, h2 192.168.20.142, h4 192.168.40.121, with the admin server static at 192.168.30.10. The DHCP exchange in the capture takes three milliseconds from Discover to ACK:

0.000000  0.0.0.0      → 255.255.255.255  DHCP Discover
0.000257  192.168.10.1 → 192.168.10.147   DHCP Offer
0.000777  0.0.0.0      → 255.255.255.255  DHCP Request
0.003095  192.168.10.1 → 192.168.10.147   DHCP ACK

Then the actual test: from the lab host, ping the admin server.

mininet> h4 ping -c 3 192.168.30.10
3 packets transmitted, 0 received, 100% packet loss

Packet loss alone doesn't prove the ACL did it; anything broken would look the same. The flow counters do. Before the test, the labs→admin rule had matched nothing:

priority=40000,ip,nw_src=192.168.40.0/24,nw_dst=192.168.30.0/24 n_packets=0 actions=drop

After it, the counter is exactly three, one per ping, while the ECE→CS rule is still at zero:

priority=40000,ip,nw_src=192.168.40.0/24,nw_dst=192.168.30.0/24 n_packets=3 n_bytes=306 actions=drop
priority=40000,ip,nw_src=192.168.10.0/24,nw_dst=192.168.20.0/24 n_packets=0 n_bytes=0   actions=drop
priority=0                                                        n_packets=169            actions=NORMAL

TCP behaves the same way. An HTTP request from the lab to the admin server shows only SYN retransmissions with growing gaps between them and never a reply: the switch drops silently, so the client assumes loss and keeps retrying until it times out. From ECE, which is allowed, the same request completes a normal three-way handshake and gets the page.

Gotchas worth knowing

Where it falls short

code, captures and full report: github.com/Mohsin-Rasul/SDN-Campus-Network