Implementing a Layer-2 Firewall using POX and Mininet


Hello there! Today we’re going to see how to implement a Layer-2 SDN Firewall using the POX Controller and Mininet. We are also going to reuse the same Tree topology we created last time (albeit with some minor changes). In case you are new to Mininet, you are highly recommended to first go through the previous blog post on using Mininet, here

Here is what you’re going to learn today -

  • How to use the Python based POX OpenFlow controller instead of the default Mininet Controller
  • Understand what an SDN based Firewall does and implement it from scratch using POX

Software Prerequisites

  • Mininet: While you can use the Mininet VM like last time, I recommend installing the package if you’re on Linux. For Ubuntu, just run: sudo apt-get install mininet in terminal and you’re good to go.
  • POX: For the POX controller you just need to clone the git repository. I’m assuming you have git installed and know how to work it. If this is not the case, this is a good resource. To clone the repository type the following in terminal and hit enter: git clone https://github.com/noxrepo/pox

SDN Firewall

A Firewall can be understood to be something that obstructs traffic coming its way and filters it according to some rules. A general Firewall can be employed to protect a network from the internet. For an SDN based Firewall, we are going to use the OpenFlow controller to filter traffic between hosts according to some rules and accordingly let it pass through or not. The way we will do this is by using the POX controller to establish our required policies or rules (we will come to these later) and filter traffic between hosts using the switches. An example of such an SDN Firewall with two hosts is shown in the figure below:

alt text

Implementation

Topology set up in Mininet

The topology we are going to implement is going to be the same as last time. To recall, here is the diagram of the topology below -

alt text

Now, we can basically use the same file as last time, tree.py but with some modifications. These will basically be minor changes like referencing a remote controller instead of the default Mininet controller and specifying simple MAC addresses for our hosts so that we may reference them easily when we create rules for the Layer-2 Firewall. To recall, the code we were working with last time in tree.py was as follows:

from mininet.net import Mininet
from mininet.node import Controller
from mininet.cli import CLI
from mininet.log import setLogLevel, info

def treeTopo():
    net = Mininet( controller=Controller )
    
    info( '*** Adding controller\n' )
    net.addController( 'c0' )

    info( '*** Adding hosts\n' )
    h1 = net.addHost( 'h1', ip='10.0.0.1' )
    h2 = net.addHost( 'h2', ip='10.0.0.2' )
    h3 = net.addHost( 'h3', ip='10.0.0.3' )
    h4 = net.addHost( 'h4', ip='10.0.0.4' )
    h5 = net.addHost( 'h5', ip='10.0.0.5' )
    h6 = net.addHost( 'h6', ip='10.0.0.6' )
    h7 = net.addHost( 'h7', ip='10.0.0.7' )
    h8 = net.addHost( 'h8', ip='10.0.0.8' )

    info( '*** Adding switches\n' )
    s1 = net.addSwitch( 's1' )
    s2 = net.addSwitch( 's2' )
    s3 = net.addSwitch( 's3' )
    s4 = net.addSwitch( 's4' )
    s5 = net.addSwitch( 's5' )
    s6 = net.addSwitch( 's6' )
    s7 = net.addSwitch( 's7' )

    info( '*** Creating links\n' )
    net.addLink( h1, s3 )
    net.addLink( h2, s3 )
    net.addLink( h3, s4 )
    net.addLink( h4, s4 )
    net.addLink( h5, s6 )
    net.addLink( h6, s6 )
    net.addLink( h7, s7 )
    net.addLink( h8, s7 )
    
    root = s1
    layer1 = [s2,s5]
    layer2 = [s3,s4,s6,s7]

    for idx,l1 in enumerate(layer1):
        net.addLink( root,l1 )
        net.addLink( l1, layer2[2*idx] )
        net.addLink( l1, layer2[2*idx + 1] )
        
    info( '*** Starting network\n')
    net.start()

    info( '*** Running CLI\n' )
    CLI( net )

    info( '*** Stopping network' )
    net.stop()

if __name__ == '__main__':
    setLogLevel( 'info' )
    treeTopo()
    

The changes we are going to make are just going to be two -

  • Changing the controller to a remote one so we may use POX. We need to first import RemoteController instead of the regular Controller in our module import statements and then we can pass that through to the Mininet class. These changes are basically -
from mininet.node import RemoteController

and,

net = Mininet( controller=RemoteController )
  • Adding MAC addresses while initializing the hosts so that we can easily define the Layer-2 rules for our Firewall. Thus, we initialize the hosts this way -
    h1 = net.addHost( 'h1', ip='10.0.0.1', mac='00:00:00:00:00:01' )            
    h2 = net.addHost( 'h2', ip='10.0.0.2', mac='00:00:00:00:00:02' )            
    h3 = net.addHost( 'h3', ip='10.0.0.3', mac='00:00:00:00:00:03' )            
    h4 = net.addHost( 'h4', ip='10.0.0.4', mac='00:00:00:00:00:04' )            
    h5 = net.addHost( 'h5', ip='10.0.0.5', mac='00:00:00:00:00:05' )            
    h6 = net.addHost( 'h6', ip='10.0.0.6', mac='00:00:00:00:00:06' )            
    h7 = net.addHost( 'h7', ip='10.0.0.7', mac='00:00:00:00:00:07' )            
    h8 = net.addHost( 'h8', ip='10.0.0.8', mac='00:00:00:00:00:08' ) 

The tree.py file now looks like this:

from mininet.net import Mininet
from mininet.node import RemoteController
from mininet.cli import CLI
from mininet.log import setLogLevel, info

def treeTopo():
    net = Mininet( controller=RemoteController )
    
    info( '*** Adding controller\n' )
    net.addController('c0')
    
    info( '*** Adding hosts\n' )
    h1 = net.addHost( 'h1', ip='10.0.0.1', mac='00:00:00:00:00:01' )
    h2 = net.addHost( 'h2', ip='10.0.0.2', mac='00:00:00:00:00:02' )
    h3 = net.addHost( 'h3', ip='10.0.0.3', mac='00:00:00:00:00:03' )
    h4 = net.addHost( 'h4', ip='10.0.0.4', mac='00:00:00:00:00:04' )
    h5 = net.addHost( 'h5', ip='10.0.0.5', mac='00:00:00:00:00:05' )
    h6 = net.addHost( 'h6', ip='10.0.0.6', mac='00:00:00:00:00:06' )
    h7 = net.addHost( 'h7', ip='10.0.0.7', mac='00:00:00:00:00:07' )
    h8 = net.addHost( 'h8', ip='10.0.0.8', mac='00:00:00:00:00:08' )

    info( '*** Adding switches\n' )
    s1 = net.addSwitch( 's1' )
    s2 = net.addSwitch( 's2' )
    s3 = net.addSwitch( 's3' )
    s4 = net.addSwitch( 's4' )
    s5 = net.addSwitch( 's5' )
    s6 = net.addSwitch( 's6' )
    s7 = net.addSwitch( 's7' )
    
    info( '*** Creating links\n' )
    net.addLink( h1, s3 )
    net.addLink( h2, s3 )
    net.addLink( h3, s4 )
    net.addLink( h4, s4 )
    net.addLink( h5, s6 )
    net.addLink( h6, s6 )
    net.addLink( h7, s7 )
    net.addLink( h8, s7 )
    
    root = s1
    layer1 = [s2,s5]
    layer2 = [s3,s4,s6,s7]
    
    for idx,l1 in enumerate(layer1):
        net.addLink( root,l1 )
        net.addLink( l1, layer2[2*idx] )
        net.addLink( l1, layer2[2*idx + 1] )
        
    info( '*** Starting network\n')
    net.start()
    
    info( '*** Running CLI\n' )
    CLI( net )
    
    info( '*** Stopping network' )
    net.stop()
    
if __name__ == '__main__':
    setLogLevel( 'info' )
    treeTopo()

Firewall Rules and POX Implementation

Firstly, we need to decide on the rules we will enforce through our firewall. It should also be noted that it will be a bi-directional block, and if a rule exists, the controller will ensure that switches neither let incoming or outgoing traffic from either host reach the other. So for our Firewall, we can have the following 4 rules:

  • h1 and h2 are mutually blocked
  • h2 and h4 are mutually blocked
  • h2 and h7 are mutually blocked
  • h3 and h8 are mutually blocked

Moving on to POX– I’m going to assume that you cloned the git repo and put it in the home directory to simplify things. Change the commands to reflect where your files are to avoid any problems.

Here is how we will proceed:

  1. cd into the pox directory and then type cd pox/misc/. This is where we will place our firewall code
  2. Create a new file called firewall.py in which we write our code:
  • First, we import all the classes we will require -
from pox.core import core
import pox.openflow.libopenflow_01 as of
from pox.lib.revent import *
from pox.lib.addresses import EthAddr
  • Then we specify our rules for the Firewall by writing down the Layer-2 addresses of the hosts. We then put each of these as rows in a list so that we may iterate over each rule separately later:
rules = [['00:00:00:00:00:01','00:00:00:00:00:02'],['00:00:00:00:00:02', '00:00:00:00:00:04'],['00:00:00:00:00:08','00:00:00:00:00:03'],['00:00:00:00:00:07','00:00:00:00:00:02']]
  • Next we create our SDNFirewall class in which the actual controller is going to be accessing and checking flows and modifying flow tables accordingly. The first function __init__ is just a constructor. The second and more interesting function, _handle_ConnectionUp will fire up each time a host tries to reach another through the switches. When this happens, we will first iterate over each rule in our list of rules. Here we create match fields by providing the two hosts in the rule, specified by rule[0] and rule[1] to block. We then create an OpenFlow flow_mod message using of.ofp_flow_mod() and set its match field to our blocking rule. At the end, we use event.connection.send(flow_mod) to send our blocking rule to the switch so that it can be enforced. Lastly, we create the launch function that POX requires and pass our SDNFirewall class to it. The code for this is as follows:
class SDNFirewall (EventMixin):
    
    def __init__ (self):
        self.listenTo(core.openflow)
        
    def _handle_ConnectionUp (self, event):
        for rule in rules:
            block = of.ofp_match()
            block.dl_src = EthAddr(rule[0])
            block.dl_dst = EthAddr(rule[1])
            flow_mod = of.ofp_flow_mod()
            flow_mod.match = block
            event.connection.send(flow_mod)
        
def launch ():
    core.registerNew(SDNFirewall)

This completes our developing the controller to fulfill our needs. The script in its entirety is shown below. Moreover, in the next section we will run our code and see if the Firewall implementation actually did what it was supposed to.

from pox.core import core
import pox.openflow.libopenflow_01 as of
from pox.lib.revent import *
from pox.lib.addresses import EthAddr

rules = [['00:00:00:00:00:01','00:00:00:00:00:02'],['00:00:00:00:00:02', '00:00:00:00:00:04'],['00:00:00:00:00:08','00:00:00:00:00:03'],['00:00:00:00:00:07','00:00:00:00:00:02']]

class SDNFirewall (EventMixin):
    
    def __init__ (self):
        self.listenTo(core.openflow)
        
    def _handle_ConnectionUp (self, event):
        for rule in rules:
            block = of.ofp_match()
            block.dl_src = EthAddr(rule[0])
            block.dl_dst = EthAddr(rule[1])
            flow_mod = of.ofp_flow_mod()
            flow_mod.match = block
            event.connection.send(flow_mod)
        
def launch ():
    core.registerNew(SDNFirewall)

Running everything

This just requires us to start our Mininet topology emulation and the POX controller with the Firewall implemented.

  • To start the Mininet emulation is simple. Just run sudo python tree.py (Don’t forget– Mininet needs to run as root) in the terminal and you should see the hosts and switches initialized and the Mininet CLI started:

alt text

  • To run the controller, first cd into the pox directory. Now type in the command given below. It is good to observe that the way POX takes in arguments is in the form of folders and files separated by ‘.’:
    • ./pox.py log.level --DEBUG openflow.of_01 forwarding.l2_learning misc.firewall

As long as you don’t see any error messages, there should be no problem.

Now, we go back to the Mininet CLI and type in pingall to see if our Firewall actually works. The pingall command will just go ahead and ping each node from every other node. Here are the results:

alt text

Yes, according to our Firewall rules, h1 and h2 were never able to connect, neither were h2 and h4, h3 and h8, h2 and h7 able to connect respectively too! We see that there is a packet loss of 14%, which is absolutely correct. Thus, we were successful in our endeavour of implementing an SDN Firewall!

Conclusion

In this post, we saw how to implement a Layer-2 Firewall using the POX Controller. Using rules specifying Layer-2 characteristics we were able to control the switches using POX to either permit traffic between hosts or restrict it. The next time, I will be discussing the P4 programming language and how it seeks to revolutionize networks completely. Stay tuned and keep being awesome!