Saturday, January 11, 2014

Physical switch hybrid OpenFlow example

Alcatel-Lucent OmniSwitch analytics driven control provided an example with a physical switch, using the Web Services API to send CLI controls to the switch as HTTP requests, the following screen shot shows the results:
Figure 1: Controller using HTTP / REST API
Integrated hybrid OpenFlow describes how the combination of normal forwarding combined with OpenFlow for control of large flows provides a scaleable and practical solution for traffic engineering. The article used the Mininet testbed to develop a DDoS mitigation controller consisting of the sFlow-RT real-time analytics engine to detect large flows and the Floodlight OpenFlow controller to push control rules to the software virtual switch in the testbed.
Figure 2: Performance aware software defined networking
The OmniSwitch supports hybrid mode OpenFlow and this article will evaluate the performance of a physical switch hybrid OpenFlow solution using the OmniSwitch. The following results were obtained when repeating the DDoS attack test using Floodlight and OpenFlow as the control mechanism:
Figure 3: OmniSwitch controller using hybrid OpenFlow
Figure 3 shows that implementing traffic controls using OpenFlow is considerably faster than those obtained using the HTTP API shown in Figure 1, cutting the time to implement controls from seconds to milliseconds.
Figure 4: Mininet controller using hybrid OpenFlow
Figure 4 shows that the physical switch results are consistent with those obtained using Mininet, demonstrating the value of network simulation as a way to develop controllers before moving them into production. In fact, the Open vSwitch virtual switch used by Mininet is integrated in the mainstream Linux kernel and is an integral part of many commercial and open source virtualization platforms, including: VMware/Nicira NSX, OpenStack, Xen Cloud Platform, XenServer, and KVM. In these environments virtual machine traffic can be controlled using Open vSwitch.

The following command arguments configure the OmniSwitch to connect to the Floodlight controller running on host 10.0.0.53:
openflow logical-switch ls1 mode api
openflow logical-switch ls1 controller 1.0.0.53:6633
openflow logical-switch ls1 version 1.0
The Floodlight web based user interface can be used to confirm that the switch is connected.
The following sFlow-RT script implements the controller:
include('extras/aluws.js');

var flowkeys = 'inputifindex,ipsource';
var value = 'frames';
var filter = 'direction=ingress&icmptype=8';
var threshold = 1000;

var metricName = 'ddos';
var controls = {};
var enabled = true;
var blockSeconds = 20;

var user = 'admin';
var password = 'password';
var sampling = 128;
var polling = 30;

var collectorIP = "10.0.0.162";
var collectorPort = 6343;

// Floodlight OpenFlow Controller REST API
var floodlight = 'http://10.0.0.53:8080/';
var listswitches = floodlight+'wm/core/controller/switches/json';
var flowpusher = floodlight+'wm/staticflowentrypusher/json';
var clearflows = floodlight+'wm/staticflowentrypusher/clear/all/json'; 

function clearOpenFlow() {
  http(clearflows);
}

function setOpenFlow(spec) {
  http(flowpusher, 'post','application/json',JSON.stringify(spec));
}

function deleteOpenFlow(spec) {
  http(flowpusher, 'delete','application/json',JSON.stringify(spec));
}

var agents = {};
function discoverAgents() {
  var res = http(listswitches);
  var dps = JSON.parse(res);
  for(var i = 0; i < dps.length; i++) {
    var dp = dps[i];
    var agent = dp.inetAddress.match(/\/(.*):/)[1];
    var ports = dp.ports;
    var nameToNumber = {};
    var names = [];
    // get ifName to OpenFlow port number mapping
    // and list of OpenFlow enabled ports
    for (var j = 0; j < dp.ports.length; j++) {
      var port = dp.ports[j];
      var name = port.name.match(/^port (.*)$/)[1];
      names.push(name);
      nameToNumber[name] = port.portNumber;
    }
    agents[agent] = {dpid:dp.dpid,names:names,nameToNumber:nameToNumber}; 
  }
}

function initializeAgent(agent) {
  var rec = agents[agent];
  var server = new ALUServer(agent,user,password);
  rec.server = server;

  var ports = rec.names.join(' ');

  server.login();

  // configure sFlow
  server.runCmds([
    'sflow agent ip ' + agent,
    'sflow receiver 1 name InMon address '+collectorIP+' udp-port '+collectorPort,
    'sflow sampler 1 port '+ports+' receiver 1 rate '+sampling,
    'sflow poller 1 port '+ports+' receiver 1 interval '+polling
  ]);

  // get ifIndex to ifName mapping
  var res = server.rest('get','mib','ifXTable',{mibObject0:'ifName'});
  var rows = res.result.data.rows;
  var ifIndexToName = {};
  for(var ifIndex in rows) ifIndexToName[ifIndex] = rows[ifIndex].ifName;

  server.logout();

  agents[agent].ifIndexToName = ifIndexToName;
}

function block(agent,ip,port) {
  if(controls[ip]) return;

  var rec = agents[agent];
  if(!rec) return;

  var name = 'block-' + ip;
  setOpenFlow({name:name,switch:rec.dpid,cookie:0,
               priority:500,active:true,
               'ether-type':'0x0800','src-ip':ip,
               actions:''});

  controls[ip] = { 
    name: name, 
    agent:agent,
    action:'block', 
    time: (new Date()).getTime() 
  };
}

function allow(ip) {
  if(!controls[ip]) return;

  deleteOpenFlow({name:controls[ip].name});

  delete controls[ip];
}

setEventHandler(function(evt) {
  if(!enabled) return;

  var agent = evt.agent;
  var parts = evt.flowKey.split(',');
  var ifindex = parts[0];
  var ipsource = parts[1];

  var rec = agents[agent];
  if(!rec) return;

  block(agent,ipsource,rec.ifIndexToName[ifindex]);
}, [metricName]);


setIntervalHandler(function() {
  // remove stale controls
  var stale = [];
  var now = (new Date()).getTime();
  var threshMs = 1000 * blockSeconds;
  for(var addr in controls) {
    if((now - controls[addr].time) > threshMs) stale.push(addr);
  }
  for(var i = 0; i < stale.length; i++) allow(stale[i]);
},10);


setHttpHandler(function(request) {
  var result = {};
  try {
    var action = '' + request.query.action;
    switch(action) {
    case 'block':
      var agent = request.query.agent[0];
      var address = request.query.address[0];
      var port = request.query.port[0];
      if(agent&&address&&port) block(agent,address,port);
      break;
    case 'allow':
      var address = request.query.address[0];
      if(address) allow(address);
      break;
    case 'enable':
      enabled = true;
      break;
    case 'disable':
      enabled = false;
      break;
    case 'clearof':
      clearOpenFlow();
      break;
     }
  }
  catch(e) { result.error = e.message }
  result.controls = controls;
  result.enabled = enabled;
  return JSON.stringify(result);
});

discoverAgents();
for(var agent in agents) {
  initializeAgent(agent);
}

setFlow(metricName,{keys:flowkeys,value:value,filter:filter});
setThreshold(metricName,{metric:metricName,value:threshold,byFlow:true,timeout:10});
The following command line argument loads the script on startup:
-D script.file=omniofddos.js
Some notes on the script:
  1. A call to the Floodlight REST API is used to discover the set of switches, their IP addresses and OpenFlow datapath identifiers, ports, port names and OpenFlow port numbers.
  2. The initializeAgent() function uses OmniSwitch Web Services API is used to configure sFlow on the switches and ports that are controllable using OpenFlow/Floodlight.
  3. The script maintains mappings between port names, ifIndex numbers and OpenFlow port numbers so that ifIndex numbers used to identify ports in sFlow can be mapped to the port identifiers used into configuration commands and OpenFlow rules.
DDoS mitigation is only one use case for large flow control, others described on this blog include: ECMP / LAG load balancing, traffic marking, blacklists, and packet capture. Scripts can be added to address these different use cases, as well as providing information on network health and server performance to operations teams (see Exporting events using syslog and Metric export to Graphite)

No comments:

Post a Comment