Simple Controller 2 to handle the failed link and re-calculate the routing table

Based on my previous work and IP Fast Reroute with Loop Free Alternate, I wrote this lab.

 

[topology]

 

There are two paths from h1 to h2, i.e. s1-s2-s4 (shortest path) and s1-s3-s5-s4. We will make the link between s1 and s2 fail when h1 pings h2. The controller will get notified that the link between s1 and s2 fails (topology changes).

The controller will clear all the routing tables in s1,s2,s3,s4,and 5, and then re-calculate the paths and installed rules in those switches.

 

[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: lpm;

        }

        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"], ["s2", "s4"], ["s3", "s5"], ["s5", "s4"],["s4", "h2"]],

    "hosts": {

      "h1": {

      },

      "h2": {

      }

    },

    "switches": {

      "s1": {

      },

      "s2": {

      },

      "s3": {

      },

      "s4": {

      },

      "s5": {

      }

    }

  }

}

 

[routing-controller2.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", [])

 

    def route(self, failures=None):

        switches = {sw_name:{} for sw_name in self.topo.get_p4switches().keys()}

        print "switches:", switches

        print "==============================================================================="

        print "self.controllers:", self.controllers

        print "==============================================================================="

            

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

            for sw_dst in self.topo.get_p4switches():

 

                #if its ourselves we create direct connections

                if sw_name == sw_dst:

                    for host in self.topo.get_hosts_connected_to(sw_name):

                        sw_port = self.topo.node_to_node_port_num(sw_name, host)

                        host_ip = self.topo.get_host_ip(host) + "/32"

                        host_mac = self.topo.get_host_mac(host)

                                    print host, "(", host_ip, host_mac, ")", "-->", sw_name, "with port:", sw_port 

 

                        #add rule

                        print "table_add at {}:".format(sw_name)

                        self.controllers[sw_name].table_add("ipv4_lpm", "set_nhop", [str(host_ip)], [str(host_mac), str(sw_port)])

 

                #check if there are directly connected hosts

                else:

                    if self.topo.get_hosts_connected_to(sw_dst):

                        graph = self.topo.network_graph

                        if failures is not None:

                          graph = graph.copy()

                          for failure in failures:

                            graph.remove_edge(*failure)

 

                        dijkstra = dict(all_pairs_dijkstra(graph, weight='weight'))

                        distances = {node: data[0] for node, data in dijkstra.items()}

                        all_shortest_paths = {node: data[1] for node, data in dijkstra.items()}

                        #print "all_shortest_paths:", all_shortest_paths

                        paths=all_shortest_paths[sw_name][sw_dst]

                        print "paths=", paths

              

                                    print sw_name,"->",sw_dst,":",paths

                        for host in self.topo.get_hosts_connected_to(sw_dst):

                            next_hop = paths[1]

                            host_ip = self.topo.get_host_ip(host) + "/24"

                            sw_port = self.topo.node_to_node_port_num(sw_name, next_hop)

                            dst_sw_mac = self.topo.node_to_node_mac(next_hop, sw_name)

 

                            #add rule

                            print "table_add at {}:".format(sw_name)

                            self.controllers[sw_name].table_add("ipv4_lpm", "set_nhop", [str(host_ip)], [str(dst_sw_mac), str(sw_port)])

                                                                  

    def failure_notification(self, failures):

        """Called if a link fails.

        Args:

            failures (list(tuple(str, str))): List of failed links.

        """

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

          self.controllers[sw_name].table_clear("ipv4_lpm")

        self.route(failures) 

 

    def main(self):

        self.route()

 

if __name__ == "__main__":

    controller = RoutingController()

    controller.main()

    CLI(controller)

 

[cli.py]

 

[execution]

 

 

No work. Because we don’t put the rules into switches.

 

 

Now h1 can ping h2.

 

We can also see that the ping from h1 to h2 will via s1-s2-s4

 

 

Make the link between s1 and s2 fail

 

We can see that the controller will re-calculate the routing paths.

s1-s3-s5-s4

s4-s5-s3-s1

 

And the ping still works.

 

Dr. Chih-Heng Ke

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

Email: smallko@gmail.com