P4-utils: Simple Controller to populate routing tables for any topology

 

Before running the examples, please refer to p4-utils. This lab also refers to Simple Routing.

 

[Topology]

In the previous examples, we need to set the routing rules in all switches. So in this example, we will run a controller with help of p4-utils. The controller can understand the network topology and set all the routing rules in all switches for any topology. If there are many shortest paths from sender to receiver. The controller only chooses the first path to send the packet.

 

[ip_forward.p4]

#include <core.p4>

#include <v1model.p4>

typedef bit<48> macAddr_t;

typedef bit<9> egressSpec_t;

 

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 {

}

 

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) {

    @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": true,

  "enable_log": true,

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

    "hosts": {

      "h1": {

      },

      "h2": {

      }

    },

    "switches": {

      "s1": {

      },

      "s2": {

      },

      "s3": {

      },

      "s4": {

      }, 

      "s5": {

      }  

    }

  }

}

 

[routing-controller.p4] Note: we don’t have cmd-s1.txt, cmd-s2.txt … for all switches.

from p4utils.utils.topology import Topology

from p4utils.utils.sswitch_API import SimpleSwitchAPI

 

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):

        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):

                        paths = self.topo.get_shortest_paths_between_nodes(sw_name, sw_dst)

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

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

                            next_hop = paths[0][1] #if there are more than one path, choose the first path

                            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 main(self):

        self.route()

 

if __name__ == "__main__":

    controller = RoutingController().main()

 

[execution]

 

h1 can not ping h2 now.

 

Open another terminal

Add the rules in s3. If matched key is equal to 10.0.1.1/24 [LPM-0a:00:01:01/24], action:set_nhop, and runtime data: 7a:a0:78:fe:b1:5d     00:01 (change the mac address and send the packet to port 1 [00:01])

Add the rules for s2.

 

Add the rules for s1. Note: You can also see that the shortest path from s1 to s5 is via s1-s3-s5.

 

Add the rules for s5

Add the rules for s4

 

After populating the rules. The ping can work.


Dr. Chih-Heng Ke

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

Email: smallko@gmail.com