P4 utils: implement the idea in “A Zero Flow Entry Expiration Timeout P4 Switch

 

[topology]

 

 

Initially, the rules have been installed in S1. If the destination ip address is 10.0.1.1, the s1 will forward the packet to the port 1 and change the destination mac address to the h1’s MAC address.

If the destination ip address is 10.0.2.1, the s1 will forward the packet to the port 2 and change the destination mac address to the h2’s MAC address. The s1 will monitor the packet field of TCP header, if this TCP has FIN or RST flag set, s1 will send the information to the controller. When controller receives the notification, the controller will delete the corresponding rule in the switch.

 

(ip_forward.p4)

#include <core.p4>

#include <v1model.p4>

typedef bit<48> macAddr_t;

typedef bit<9> egressSpec_t;

 

#define TCP_FLAG_FIN 0x01

#define TCP_FLAG_RST 0x04

 

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;

}

 

header tcp_t {

    bit<16> srcPort;

    bit<16> dstPort;

    bit<32> seqNo;

    bit<32> ackNo;

    bit<4>  dataOffset;

    bit<4>  res;

    bit<8>  flags;

    bit<16> window;

    bit<16> checksum;

    bit<16> urgentPtr;

}

 

struct metadata {

}

 

struct headers {

    arp_t      arp;

    ethernet_t ethernet;

    ipv4_t     ipv4;

    tcp_t      tcp;     

}

 

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 select(hdr.ipv4.protocol) {

            8w6: parse_tcp;

            default: accept;

        }

    }

     state parse_tcp {

        packet.extract(hdr.tcp);

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

            if (hdr.tcp.isValid()){

                if(hdr.tcp.flags & TCP_FLAG_FIN == TCP_FLAG_FIN){

                    clone(CloneType.I2E,100);

                 }

                 if(hdr.tcp.flags & TCP_FLAG_RST == TCP_FLAG_RST){

                    clone(CloneType.I2E,100);

                 }

            }

        }

    }

}

 

control DeparserImpl(packet_out packet, in headers hdr) {

    apply {

        packet.emit(hdr.ethernet);

        packet.emit(hdr.arp);

        packet.emit(hdr.ipv4);

        packet.emit(hdr.tcp);

    }

}

 

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": "manual",

    "auto_arp_tables": "true",

    "auto_gw_arp": "true",

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

    "hosts": {

      "h1": {

        "ip": "10.0.1.1/24",

        "gw": "10.0.1.254"

      },

      "h2": {

        "ip": "10.0.2.1/24",

       "gw": "10.0.2.254"

      }

    },

    "switches": {

      "s1": {

        "cli_input": "cmd.txt",

        "program": "ip_forward.p4",

        "cpu_port": true

      }

    }

  }

}

 

[cmd.txt]

table_set_default dmac _drop

table_add ipv4_lpm set_nhop 10.0.1.1/32 => 00:00:0a:00:01:01 1

table_add ipv4_lpm set_nhop 10.0.2.1/32 => 00:00:0a:00:02:01 2

mirroring_add 100 3

 

[controller1.py]

import nnpy

import struct

from p4utils.utils.topology import Topology

from p4utils.utils.sswitch_API import SimpleSwitchAPI

from scapy.all import *

 

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

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

               print "number of entries for", sw_name, ":", self.controllers[sw_name].table_num_entries("ipv4_lpm") 

 

    def recv_msg_cpu(self, pkt):

        global del_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_dst not in del_rules:

                del_rules.append(ip_dst)

          else:

                return

 

        match_ip=ip_dst+"/32"

        self.controllers["s1"].table_delete_match("ipv4_lpm", [match_ip])

     

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

 

[Execution]

 

 

Open two terminals for h1 and h2

 

Open another terminal to run the controller

 

Run http server at h2 and curl at h1. We can also see that the rules have been deleted from s1 when http request has been finished.

 

Re-run the curl at h1. And we can see that there is no response from h2.

 

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

Department of Computer Science and Information Engineering,

National Quemoy University, Kinmen, Taiwan.