Simple controller 3 to handle the failed link (like fast failover)

 

Based on my previous work, Test Fast-Failover Group in OpenFlow 1.3, and IP Fast Reroute with Loop Free Alternate, I wrote this lab.

 

[Topology]

There are two paths from h1 to h2, i.e. h1-s1-s2-h2 and h1-s1-s3-s2-h2. Initially, we will set two rules in s1 that the destination is h2 to different priorities. The path via s2 has higher priority than the path vias s3. When the link between s1 and s2 fails, the controller will delete the rule that is via s2 to h2 in s1. So the packets that go to h2 will go via s3.

 

[ip_forward.p4]

#include <core.p4>

#include <v1model.p4>

typedef bit<48> macAddr_t;

typedef bit<9> egressSpec_t;

 

#define N_PORTS 512

 

header arp_t {

    bit<16> htype;

    bit<16> ptype;

    bit<8>  hlen;

    bit<8>  plen;

    bit<16> opcode;

    bit<48> hwSrcAddr;

    bit<32> protoSrcAddr;

    bit<48> hwDstAddr;

    bit<32> protoDstAddr;

}

 

header ethernet_t {

    bit<48> dstAddr;

    bit<48> srcAddr;

    bit<16> etherType;

}

 

header ipv4_t {

    bit<4>  version;

    bit<4>  ihl;

    bit<8>  diffserv;

    bit<16> totalLen;

    bit<16> identification;

    bit<3>  flags;

    bit<13> fragOffset;

    bit<8>  ttl;

    bit<8>  protocol;

    bit<16> hdrChecksum;

    bit<32> srcAddr;

    bit<32> dstAddr;

}

 

struct metadata {

    bit<1> linkState;

}

 

struct headers {

    @name(".arp")

    arp_t      arp;

    @name(".ethernet")

    ethernet_t ethernet;

    @name(".ipv4")

    ipv4_t     ipv4;

}

 

parser ParserImpl(packet_in packet, out headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {

    @name(".parse_arp") state parse_arp {

        packet.extract(hdr.arp);

        transition accept;

    }

    @name(".parse_ethernet") state parse_ethernet {

        packet.extract(hdr.ethernet);

        transition select(hdr.ethernet.etherType) {

            16w0x800: parse_ipv4;

            16w0x806: parse_arp;

            default: accept;

        }

    }

    @name(".parse_ipv4") state parse_ipv4 {

        packet.extract(hdr.ipv4);

        transition accept;

    }

    @name(".start") state start {

        transition parse_ethernet;

    }

}

 

control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {

    apply {

    }

}

 

control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {

    register<bit<1>>(N_PORTS) linkState;

  

    @name(".set_nhop") action set_nhop(macAddr_t dstAddr, egressSpec_t port) {

        //set the src mac address as the previous dst, this is not correct right?

        hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;

 

        //set the destination mac address that we got from the match in the table

        hdr.ethernet.dstAddr = dstAddr;

 

        //set the output port that we also get from the table

        standard_metadata.egress_spec = port;

 

        //decrease ttl by 1

        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;

    }

    @name("._drop") action _drop() {

        mark_to_drop(standard_metadata);

    }

    @name(".ipv4_lpm") table ipv4_lpm {

        actions = {

            set_nhop;

            _drop;

        }

        key = {

            hdr.ipv4.dstAddr: ternary;

        }

        size = 512;

        const default_action = _drop();

    }

    apply {

       if (hdr.ipv4.isValid()){

         ipv4_lpm.apply();

       }

    }

}

 

control DeparserImpl(packet_out packet, in headers hdr) {

    apply {

        packet.emit(hdr.ethernet);

        packet.emit(hdr.arp);

        packet.emit(hdr.ipv4);

    }

}

 

control verifyChecksum(inout headers hdr, inout metadata meta) {

    apply {

        verify_checksum(true, { hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv, hdr.ipv4.totalLen, hdr.ipv4.identification, hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol, hdr.ipv4.srcAddr, hdr.ipv4.dstAddr }, hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);

    }

}

 

control computeChecksum(inout headers hdr, inout metadata meta) {

    apply {

        update_checksum(true, { hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv, hdr.ipv4.totalLen, hdr.ipv4.identification, hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol, hdr.ipv4.srcAddr, hdr.ipv4.dstAddr }, hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);

    }

}

 

V1Switch(ParserImpl(), verifyChecksum(), ingress(), egress(), computeChecksum(), DeparserImpl()) main;

 

[p4app.json]

{

  "program": "ip_forward.p4",

  "switch": "simple_switch",

  "compiler": "p4c",

  "options": "--target bmv2 --arch v1model --std p4-16",

  "switch_cli": "simple_switch_CLI",

  "cli": true,

  "pcap_dump": false,

  "enable_log": false,

  "topo_module": {

    "file_path": "",

    "module_name": "p4utils.mininetlib.apptopo",

    "object_name": "AppTopoStrategies"

  },

  "controller_module": null,

  "topodb_module": {

    "file_path": "",

    "module_name": "p4utils.utils.topology",

    "object_name": "Topology"

  },

  "mininet_module": {

    "file_path": "",

    "module_name": "p4utils.mininetlib.p4net",

    "object_name": "P4Mininet"

  },

  "topology": {

    "assignment_strategy": "mixed",

    "auto_arp_tables": "true",

    "auto_gw_arp": "true",

    "links": [["h1", "s1"], ["s1", "s2"], ["s1", "s3"], ["s3", "s2"],["s2", "h2"]],

    "hosts": {

      "h1": {

      },

      "h2": {

      }

    },

    "switches": {

      "s1": {

         "cli_input": "cmd1.txt"

      },

      "s2": {

         "cli_input": "cmd2.txt"

      },

      "s3": {

         "cli_input": "cmd3.txt"

      }

    }

  }

}

 

[cmd1.txt] rules for s1. The last parameter is the priority. 0 means the highest priority. The destination address is 10.0.2.2. The second rule for destination h2 is via s2. The third rule for destination h2 is via s3. But the second rule has higher priority. So the third rule will not be used. But when the second rule is deleted, the third rule will take effect.

table_add ipv4_lpm set_nhop 0x0a000101&&&0xffffffff => 00:00:0a:00:01:01 1 0

table_add ipv4_lpm set_nhop 0x0a000202&&&0xffffffff => 00:00:00:00:02:00 2 0

table_add ipv4_lpm set_nhop 0x0a000202&&&0xffffffff => 00:00:00:00:03:00 3 1

 

[cmd2.txt] rules for s2.

table_add ipv4_lpm set_nhop 0x0a000101&&&0xffffffff => 00:00:00:00:01:00 1 0

table_add ipv4_lpm set_nhop 0x0a000101&&&0xffffffff => 00:00:00:00:03:00 2 1

table_add ipv4_lpm set_nhop 0x0a000202&&&0xffffffff => 00:00:0a:00:02:02 3 0

 

[cmd3.txt] rules for s3.

table_add ipv4_lpm set_nhop 0x0a000101&&&0xffffffff => 00:00:00:00:01:00 1 0

table_add ipv4_lpm set_nhop 0x0a000202&&&0xffffffff => 00:00:00:00:02:00 2 0

 

[routing_controller3.py]

from p4utils.utils.topology import Topology

from p4utils.utils.sswitch_API import SimpleSwitchAPI

from networkx.algorithms import all_pairs_dijkstra

from cli import CLI

 

class RoutingController(object):

 

    def __init__(self):

 

        self.topo = Topology(db="topology.db")

        self.controllers = {}

        self.init()

 

    def init(self):

        self.connect_to_switches()

        #self.reset_states()

        #self.set_table_defaults()

 

    def reset_states(self):

        [controller.reset_state() for controller in self.controllers.values()]

 

    def connect_to_switches(self):

        for p4switch in self.topo.get_p4switches():

            thrift_port = self.topo.get_thrift_port(p4switch)

            self.controllers[p4switch] = SimpleSwitchAPI(thrift_port)

 

    def set_table_defaults(self):

        for controller in self.controllers.values():

            controller.table_set_default("ipv4_lpm", "_drop", [])

  

    #when we type “fail s1 s2” in the cli, this function will be called

    def failure_notification(self, failures):

        for sw_name, controller in self.controllers.items():

          if str(sw_name)=="s1":

             #the second rule in s1 will be deleted. 0 is the first rule.

             self.controllers[sw_name].table_delete("ipv4_lpm", 1)

          if str(sw_name)=="s2":

             #the first rule in s2 will be deleted.   

             self.controllers[sw_name].table_delete("ipv4_lpm", 0)   

 

 

if __name__ == "__main__":

    controller = RoutingController()

    CLI(controller)

 

[cli.py]

Note: add one line code in cli.py. This will call failure_notification in the controller when we fail the link.

def do_fail(self, line=""):

        """Fail a link between two nodes.

 

        Usage: fail_link node1 node2

        """

        try:

            node1, node2 = line.split()

            link = (node1, node2)

        except ValueError:

            print "Provide exactly two arguments: node1 node2"

            return

 

        for node in (node1, node2):

            if node not in self.controller.controllers:

                print "%s is not a valid node!" % node, \

                    "You can only fail links between switches"

                return

 

        if node2 not in self.controller.topo[node1]:

            print "The link %s-%s does not exist." % link

            return

 

        failed_links = self.check_all_links()

        for failed_link in failed_links:

            if failed_link in [(node1, node2), (node2, node1)]:

                print "The link %s-%s is already down!" % (node1, node2)

                return

 

        print "Failing link %s-%s." % link

 

        self.update_interfaces(link, "down")

        self.update_linkstate(link, "down")

        #add the following code

        self.do_notify()

 

[execution]

Open one terminal to run the script

 

Open another terminal to run the controller.

 

In the first terminal, make h1 ping h2.

 

In the second terminal, fail the link between s1 and s2.

 

In the first terminal, we can see that the ping still works.

 

Check the rules in s1. Open another terminal. We can see that the second rule is deleted ( no entry 0x1).

 

Dr. Chih-Heng Ke

Department of Computer Science and Information Engineering, National Quemoy University, Kinmen, Taiwan

Email: smallko@gmail.com