SDN is a revolutionary idea in computer networking that ensures significant flexibility and simplicity in network control and management, apart from giving a broader scope for innovation through programmability. SDN is all about controlling the network through software or, in other words, making the network programmable.
There has been a dramatic increase in Internet usage over the past few years, as compared to the use of networking technology, which hasnt been engaged as much. SDN has been introduced as a replacement for conventional networking, to meet market requirements. Fortunately, SDN couldnt have evolved at a better time. Since the use of cloud computing is increasing, it is necessary to automate the configurations as much as possible.
According to the ONF (Open Network Foundation) definition, SDN is actually a decoupling of the network control plane and the forwarding plane. With the demand for cloud computing increasing, SDN has evolved as the most efficient way of controlling networking using some high level language to make the programming as flexible as possible.
Traditional networking versus SDN
The main difference between traditional networking and SDN is the way in which data is handled and forwarded. Unlike traditional networking, SDN has separate devices called SDN controllers which control the data path. These control the path of packets coming to switches known as the OFvSwitch (Open Flow Virtual Switch). So, there has to be some interface between the forwarding plane and the controlling plane, which is provided by the OF (Open Flow) protocol. SDN enables admins to control the way switches handle the data, provide QoS (Quality of Service), and automate the process to make it less tedious and erroneous.
Advantages of SDN over traditional networking
SDN has many advantages over traditional networking:
a. Due to the introduction of some automation in the process of networking through SDN, scalability has been increased significantly, which is also a critical requirement of the current market.
b. Unlike conventional networking, SDN only requires one centralised control plane which offsets the cost of the forwarding plane.
c. VM migration becomes easier.
d. Automating the configuration is possible.
e. Quality of Service can be provided in a more efficient way.
As shown in Figure 2, the SDN controller provides a programmable interface to the OF switches. With the help of this interface, different network applications can be written to control, manage and offer new functionalities. A recent study of several OpenFlow implementations, conducted on a large emulated network with 100,000 hosts and 256 switches, revealed that all the controllers were able to handle about 50,000 new flow requests per second. Besides, according to research going on at Stanford University, the new architecture will support around 20 million requests per second with about 5,000 switches. Multiple controllers can be used for scalability purposes; these also allow backup controllers to overcome failures and provide almost 100 per cent uptime.
Configuring an existing basic controller (POX controller) in Python
POX is an open source development platform for Python-based software defined networking (SDN) control applications, such as OpenFlow SDN controllers. POX, which enables rapid development and prototyping, is now being more commonly used than NOX, a sister project which is in C/C++.
In this article, well configure a basic SDN controller (POX) in Python, and emulate using an open source emulator called Mininet, which provides the functionality to create and operate a virtual network and control it using a controller. To set up the environment for a Mininet emulator, follow the steps described in the article that appeared in the September 2015 issue of OSFY. Quick commands to run on the terminal are given below for installation purposes (these commands have been tested on Ubuntu 14.04 and might change for other versions or distributions):
sudo apt-get install mininet
For installation from the source, type:
git clone git://github.com/mininet/mininet cd mininet mininet/util/install.sh [options]
where options include the installation of various packages along with Mininet, which can be seen using the Help option.
After the successful installation of Mininet (along with the POX controller), we can start POX using the following command:
./pox.py log.level DEBUG misc.of_tutorial
The above command tells the POX controller to enable verbose logging and to start the of_tutorial file, which acts as an Ethernet hub right now.
Now, start the Mininet openflow tool to perform experiments using the following command line:
sudo mn topo single, 3 --mac --switch ovsk --controller remote
After successfully starting, it will show the message that switches have been connected with the {MAC address}. To verify the connections established by default, use the following command:
pingall
Here, all the hosts will be unreachable to one another. This is because we have set the remote controller, but have not initiated it or, in other words, there is no entry in the controller for how to handle incoming packets at the switches. Thus, when OF-enabled switches ask for the decision from the controller, it simply gives the command to drop the packets. Figure 3 shows a snapshot of the following command:
h1 ping c3 h2
We can now control each and every host of the network in the emulator, virtually. For that, we can use the following command:
xterm h1 h2 h3
Three small terminal-like windows will pop up. To check that everything is working fine, we can just capture the packages. Assuming that support for Mininet in Wireshark is also installed, run the following commands:
tcpdump -XX -n -I h1-eth0 tcpdump -XX -n -I h2-eth0
Now, we are monitoring the traffic on h1 and h2 hosts. Lets run the ping command in xterm of h3.
ping c1 10.0.0.1
After running this command, we can see the ARP requests in the h1 terminal.
Customising an existing POX controller to act as a firewall and load balancer
Till now, we were making the controller work like a hub. To make it work like a switch or learning switch, we can make changes in the file of_tutorial, and replace the statement act_like_hub() to act_like_switch().
An SDN based firewall
For a firewall kind of application, we need to have a mediator that can filter out the packets based on some conditions. Let us consider an example with MAC address filtration. For that we require to build a look-up table that can hold the MAC addresses of all the devices allowed to communicate or transfer data.
To make the controller communicate with switches, the former needs to send the messages to the latter. As and when a connection initiates a switch, even ConnectionUp is fired. Thus, tutorial code creates a Connection object, which can later be used to send the messages to the switches using the function connection.send(). At that time, we can decide the default flow using the ofp_action_output function, as shown below:
out_action = of.ofp_action_output (port = of.OFPP_FLOOD)
Here, OFPP_FLOOD even decides on flooding, which means that the packets will be forwarded to every port except the one on which the packet had arrived.
A firewall needs to restrict the packets on the basis of several other criteria. For that, we need to create the object ofp_match class. Some important fields of this class are dl_src, dl_dst and in_port. Here, dl represents the data link layer, which means src and dst are MAC addresses of the source and the destination, respectively. To send the packet, we need to send a message using ofp_packet_out. The send_packet() method can be used in of_tutorial.
Here is an example:
def send_packet (self, buffer_id, raw_data, out_port, in_port): Sends the packet out of the specified port.
If buffer_id is a valid buffer on the switch, use it; otherwise, send the raw data in raw_data.
The in_port is the port number that the packet arrived on. Use OFPP_NONE if the packet is generated by you.
msg = of.ofp_packet_out() msg.in_port = in_port if buffer_id != -1 and buffer_id is not None: msg.buffer_id = buffer_id else: if raw_data is None: return msg.raw_data = raw_data action = of.ofp_action_output (port = out_port) msg.actions.append (action) self.connection.send(msg)
This function gives the command to the switch to enter the flow table entry. Now, to take appropriate action at the switch, we need to create the entry for the packet that we want to route. Lets say we want to route a packet coming to input port = 2. So, we need to create a matching object for that, which is given by ofp_match. To create the entry in the switch, write the following code:
fm = of.ofp_flow_mod() fm.match_in_port = 2 fm.actions.append(of.ofp_action_output(port = 4))
Thus, as and when a packet comes at the port = 2, it will be redirected to port = 4. We can definitely add more parameters as per the requirement, like idle_timeout, hard_timeout, actions, priority, buffer_id, match, in_port. But we have not included all of them — just to avoid complexity.
An SDN based load balancer using round robin scheduling
As writing a load balancer could be a tedious task, we can use a template of an existing controller which forwards a request to the available servers randomly. The template can be found at https://github.com/noxrepo/pox/blob/carp/pox/misc/ip_loadbalancer.py
The main role of the controller is to select the server to which the incoming requests are to be forwarded. This part is coded in the function of the template called pick_server. It is found to be at Line No. 190 in the standard template. Thus, we can modify that procedure to customise the default policy.
The code below refers to Line No. 190 of the file ip_loadbalancer.py:
def _pick_server (self, key, inport): Pick a server for a (hopefully) new connection return random.choice(self.live_servers.keys())
The above lines show the random selection of the servers for forwarding. Now we are going to change this method to remove the randomness and add the round robin algorithm to the load balancer. First, we will add a new variable called selected_server to class iplb (which is the class containing the above method, i.e., pick_server() ). The selected_server will be an instance variable defaulting to 0, which will keep track of the state of the server allocated to fulfill the immediate previous request.
So, a modified class definition of iplb will be as shown below:
class iplb: def __init__(self,...): ... ... self.selected_server = 0
Here, __init__ is the constructor and the self inside the method refers to the instance of the object. So is the case with anything defined with self. Prefixed can be used as an instance variable (similar to other object-oriented languages such as Java). So now, to change the pick_server method, run the following commands:
def _pick_server (self, key, inport):
Pick a server for a (hopefully) new round robin based connection.
all_keys=self.live_servers.keys() if self.selected_server==len(self.live_servers): self.selected_server=0 redirected_s=all_keys[self.selected_server] self.selected_server+=1 return redirected_s
The above code first gets all the keys from the self.live_servers (which is a Python dictionary, i.e., a Hash Map). Then, in the next line, it makes sure that the selected_server isnt out of range to the total number of servers. Finally, it gets the key of the selected server and then increments the variable for the next call.
References
[1] http://www.blackstratus.com/blog/traditional-software-defined-networking/
[2] https://globalconfig.net/software-defined-networking-vs-traditional/
[3] https://www.opennetworking.org/sdn-resources/sdn-definition
[4] https://openflow.stanford.edu/display/ONL/POX+Wiki
[5] https://github.com/noxrepo/pox
[6] https://www.opennetworking.org/images/stories/downloads/sdn-resources/white-papers/wp-sdn-newnorm.pdf
[7] http://mininet.org/sample-workflow/