P4 utils: implement the PacketIn function

 

[topology]

Only default rule is store in s1, s2, s3. The default rule is “send the packet to the controller”. So when a packet comes into the switch, the switch will send this packet to the controller. The controller will setup all the rules in the switches along the transmission path. Then this packet will be sent back to the switch to be forwarded to the next switch.

 

(send_to_cpu.p4)

/* -*- P4_16 -*- */

#include <core.p4>

#include <v1model.p4>

 

const bit<16> TYPE_IPV4 = 0x800;

 

/*************************************************************************

*********************** H E A D E R S  ***********************************

*************************************************************************/

 

typedef bit<9>  egressSpec_t;

typedef bit<48> macAddr_t;

typedef bit<32> ip4Addr_t;

 

header ethernet_t {

    macAddr_t dstAddr;

    macAddr_t srcAddr;

    bit<16>   etherType;

}

 

header ipv4_t {

    bit<4>    version;

    bit<4>    ihl;

    bit<8>    tos;

    bit<16>   totalLen;

    bit<16>   identification;

    bit<3>    flags;

    bit<13>   fragOffset;

    bit<8>    ttl;

    bit<8>    protocol;

    bit<16>   hdrChecksum;

    ip4Addr_t srcAddr;

    ip4Addr_t dstAddr;

}

 

struct metadata {   

}

 

struct headers {

    ethernet_t   ethernet;

    ipv4_t       ipv4;

}

 

/*************************************************************************

*********************** P A R S E R  ***********************************

*************************************************************************/

 

parser MyParser(packet_in packet,

                out headers hdr,

                inout metadata meta,

                inout standard_metadata_t standard_metadata) {

 

    state start {

 

        packet.extract(hdr.ethernet);

        transition select(hdr.ethernet.etherType){

 

            TYPE_IPV4: ipv4;

            default: accept;

        }

 

    }

 

    state ipv4 {

 

        packet.extract(hdr.ipv4);

        transition accept;

    }

 

}

 

 

/*************************************************************************

************   C H E C K S U M    V E R I F I C A T I O N   *************

*************************************************************************/

 

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

    apply {  }

}

 

 

/*************************************************************************

**************  I N G R E S S   P R O C E S S I N G   *******************

*************************************************************************/

 

control MyIngress(inout headers hdr,

                  inout metadata meta,

                  inout standard_metadata_t standard_metadata) {

 

    action drop() {

        mark_to_drop(standard_metadata);

    }

 

    action ipv4_forward(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;

 

    }

 

    action to_cpu(egressSpec_t port) {

        standard_metadata.egress_spec = port;

    }

 

    table ipv4_lpm {

        key = {

            hdr.ipv4.dstAddr: lpm;

        }

        actions = {

            ipv4_forward;

            to_cpu;   

            drop;

        }

        size = 1024;

    }

 

    apply {

 

        //only if IPV4 the rule is applied. Therefore other packets will not be forwarded.

        if (hdr.ipv4.isValid()){

            ipv4_lpm.apply();

 

        }

    }

}

 

/*************************************************************************

****************  E G R E S S   P R O C E S S I N G   *******************

*************************************************************************/

 

control MyEgress(inout headers hdr,

                 inout metadata meta,

                 inout standard_metadata_t standard_metadata) {

    apply {

    }

}

 

/*************************************************************************

*************   C H E C K S U M    C O M P U T A T I O N   **************

*************************************************************************/

 

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

     apply {

        update_checksum(

            hdr.ipv4.isValid(),

            { hdr.ipv4.version,

              hdr.ipv4.ihl,

              hdr.ipv4.tos,

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

    }

}

 

 

/*************************************************************************

***********************  D E P A R S E R  *******************************

*************************************************************************/

 

control MyDeparser(packet_out packet, in headers hdr) {

    apply {

 

        //parsed headers have to be added again into the packet.

        packet.emit(hdr.ethernet);       

        packet.emit(hdr.ipv4);

 

    }

}

 

/*************************************************************************

***********************  S W I T C H  *******************************

*************************************************************************/

 

//switch architecture

V1Switch(

MyParser(),

MyVerifyChecksum(),

MyIngress(),

MyEgress(),

MyComputeChecksum(),

MyDeparser()

) main;

 

[controller.py]

import nnpy

import struct

from p4utils.utils.topology import Topology

from p4utils.utils.sswitch_API import SimpleSwitchAPI

#from scapy.all import Ether, sniff, Packet, BitField

from scapy.all import *

 

rules=[]

 

class myController(object):

 

    def __init__(self):

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

        self.controllers = {}

        self.connect_to_switches()

       

    def connect_to_switches(self):

        for p4switch in self.topo.get_p4switches():

            thrift_port = self.topo.get_thrift_port(p4switch)

            #print "p4switch:", p4switch, "thrift_port:", thrift_port

            self.controllers[p4switch] = SimpleSwitchAPI(thrift_port)   

 

    def recv_msg_cpu(self, pkt):

        print "-------------------------------------------------------------------"

        global rules

        print "interface:", pkt.sniffed_on

        print "summary:", pkt.summary()

        if IP in pkt:

          ip_src=pkt[IP].src

          ip_dst=pkt[IP].dst

          print "ip_src:", ip_src, " ip_dst:", ip_dst

          if (ip_src, ip_dst) not in rules:

            rules.append((ip_src, ip_dst))

            print "rules:", rules

          else:

            return

      

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

        #print "switches:", switches

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

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

           host_ip_addr = self.topo.get_host_ip(host)

           if ip_src == host_ip_addr:

             sw_src = sw_name

            

           if ip_dst == host_ip_addr:

             sw_dst = 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", "ipv4_forward", [str(host_ip)], [str(host_mac), str(sw_port)])

 

        print "sw_src:", sw_src, "sw_dst:", sw_dst  

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

        sw_1=sw_src

        for next_hop in paths[0][1:]:

         host_ip = ip_dst + "/32"

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

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

         #add rule

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

         self.controllers[sw_1].table_add("ipv4_lpm", "ipv4_forward", [str(host_ip)],

                                                    [str(dst_sw_mac), str(sw_port)])

         sw_1=next_hop

 

        print "send original packet back from ", pkt.sniffed_on

        sendp(pkt, iface=pkt.sniffed_on, verbose=False)

           

 

 

     

    def run_cpu_port_loop(self):

        cpu_interfaces = [str(self.topo.get_cpu_port_intf(sw_name).replace("eth0", "eth1")) for sw_name in self.controllers]

        sniff(iface=cpu_interfaces, prn=self.recv_msg_cpu)

       

if __name__ == "__main__":

    controller = myController()

    controller.run_cpu_port_loop()

 

[p4app.json]

{

  "program": "send_to_cpu.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,

  "cpu_port": true,

  "topo_module": {

    "file_path": "",

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

    "object_name": "AppTopo"

  },

  "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": {

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

    "hosts": {

      "h1": {

      },

      "h2": {

      }

    },

    "switches": {

      "s1": {

        "cli_input": "s1-commands.txt",

        "program": "send_to_cpu.p4",

        "cpu_port": true

      },

      "s2": {

        "cli_input": "s2-commands.txt",

        "program": "send_to_cpu.p4",

        "cpu_port": true

      },

      "s3": {

        "cli_input": "s3-commands.txt",

        "program": "send_to_cpu.p4",

        "cpu_port": true

      }          

    }

  }

 

}

 

[s1-commands.txt]

table_set_default ipv4_lpm to_cpu 3

 

[s2-commands.txt]

table_set_default ipv4_lpm to_cpu 3

 

[s3-commands.txt]

table_set_default ipv4_lpm to_cpu 3

 

[Execution]

 

Open another terminal to run the controller

 

xterm h1 h2 to run http application

 

In the controller’s terminal

 

Open another terminal. You can see that two routing rules have been installed in s1 (port=9090)

 

Dr. Chih-Heng Ke (smallko@gmail.com)

Department of Computer Science and Information Engineering,

National Quemoy University, Kinmen, Taiwan.