P4-utils: Monitor Link BW

 

[Topology]

In this example, h1 will send the traffic via s1,s3,s5 to h2. We will see the link bw.

 

[ip_forward.p4]

#include <core.p4>

#include <v1model.p4>

typedef bit<48> macAddr_t;

typedef bit<9> egressSpec_t;

const bit<4> MAX_PORT = 5;

 

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

    counter((bit<32>)MAX_PORT, CounterType.packets_and_bytes) egressPortCounter;

    apply {

        egressPortCounter.count((bit<32>)standard_metadata.egress_port);  

    }

}

 

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]

from p4utils.utils.topology import Topology

from p4utils.utils.sswitch_API import SimpleSwitchAPI

import time

import datetime

from collections import defaultdict

 

class RoutingController(object):

 

    def __init__(self):

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

        self.controllers = {}

        self.init()

        self.prev_time = 0

        self.port_bytes = defaultdict(lambda:defaultdict(lambda:None))

 

    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 addtwodimdict(thedict, key_a, key_b, val):

        if key_a in adic:

            thedict[key_a].update({key_b: val})

        else:

            thedict.update({key_a:{key_b: val}})

 

    def monitor_links(self):

        now = time.time()

        diff = now - self.prev_time

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

            for intf, node in self.topo.get_interfaces_to_node(sw_name).items():

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

                if self.port_bytes[sw_name][sw_port] is not None:

                  a=self.controllers[sw_name].counter_read("egressPortCounter", sw_port)

                  b=self.port_bytes[sw_name][sw_port]

                  bw = (a.bytes - b.bytes) * 8 /diff/1000.0;

                  print "sw_name:", sw_name, "intf:", intf, "bw:", bw, "kbps"

                self.port_bytes[sw_name][sw_port]=self.controllers[sw_name].counter_read("egressPortCounter", sw_port);            

        self.prev_time=now

               

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

        while True:

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

         print(datetime.datetime.now())

          self.monitor_links()

          time.sleep(1)

 

if __name__ == "__main__":

    controller = RoutingController().main()

 

Execution

 

Open another terminal to run controller.

Run iperf in h1 and h2

 

In controller terminal (we can see that the packets go through s1-eth3, s3-eth2, and s5-eth3. The Link BW is around 107 kbps.)

 

p.s. You can try other topologies. I think the controller can still work.

 

Dr. Chih-Heng Ke

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

Email: smallko@gmail.com