P4 switch: simple token bucket shaper

 

Based on The P4 offloading switch (https://github.com/Laraltair/p4-bos), I write a simple token bucket shaper in P4 switch.

 

[topology]

H1(10.0.1.1)---s1----(1Mbps)---s2----H2 (10.0.2.2)

 

I will set a rate limit to 12500 bytes/sec (~100kbps) when h1 sends the traffic to H2 with IP=10.0.2.2 and port =5555. For other connections, the throughput will be limited by the bottleneck link (s1-s2), i.e. 1Mbps.

 

[topology.py]

{

    "hosts": [

        "h1",

        "h2"

    ],

    "switches": {

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

            "s2": { "cli_input" : "s2-commands.txt" }

    },

    "links": [

        ["h1", "s1"], ["s1", "s2", 0, 1], ["s2","h2"]

    ]

}

 

[s1-commands.txt]

table_add ipv4_lpm ipv4_forward 10.0.2.0/24 => 00:00:00:00:02:00 2

table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 1

table_add acquire_token read_token_register =>

table_add match_ip_udp setflag 10.0.2.2 5555 =>

 

[s2-commands.txt]

table_add ipv4_lpm ipv4_forward 10.0.1.0/24 => 00:00:00:00:01:00 2

table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 1

table_add acquire_token read_token_register =>

 

[basic.p4]

#include <core.p4>

#include <v1model.p4>

 

typedef bit<48> EthernetAddress;

typedef bit<32> IPv4Address;

 

header Ethernet_h {

    EthernetAddress dst;

    EthernetAddress src;

    bit<16> ethernetType;

}

 

header IPv4_h {

    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;

    IPv4Address srcAddr;

    IPv4Address dstAddr;

}

 

header UDP_h {

    bit<16> srcPort;

    bit<16> dstPort;

    bit<16> udplength;

    bit<16> checksum;

}

 

struct headers {

    Ethernet_h ethernet;

    IPv4_h ipv4;

    UDP_h udp;

}

 

struct metadata {

    bit<1> flag;

}

 

error {

    Ipv4ChecksumError

}

 

parser bo_Parser(packet_in pkt, out headers hdr,

                    inout metadata meta, inout standard_metadata_t stdmeta)

{

    state start {

        pkt.extract(hdr.ethernet);

        transition select(hdr.ethernet.ethernetType) {

            0x0800 : parse_ipv4;

            default : accept;

        }

    }

 

    state parse_ipv4 {

        pkt.extract(hdr.ipv4);

        transition select(hdr.ipv4.protocol) {

            0x11 : parse_udp;

            default : accept;

        }

    }

 

    state parse_udp {

        pkt.extract(hdr.udp);

        transition accept;

    }

 

}

 

 

control bo_Ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta)

{

    register<bit<32>>(1) rgt;

 

    bit<32> wt_1_position = 0;

 

    bit<32> wt_1 = 0;

 

    action drop() {

      mark_to_drop();

    }

 

    action ipv4_forward(bit<48> dstAddr, bit<9> port) {

        stdmeta.egress_spec = port;

        hdr.ethernet.src=hdr.ethernet.dst;

        hdr.ethernet.dst=dstAddr;

        hdr.ipv4.ttl=hdr.ipv4.ttl-1;

    }

  

    action read_token_register() {

        rgt.read(wt_1, wt_1_position);

    }

 

    action setflag(){

        meta.flag=1;

    }      

 

    action write_wt_1() {       

        rgt.write(wt_1_position, wt_1 - stdmeta.packet_length);

    }

 

    table ipv4_lpm {

        key = {

            hdr.ipv4.dstAddr:lpm;

        }

        actions = {

            ipv4_forward;

            drop;

        }

        default_action = drop();

    }      

 

    table match_ip_udp {

        key = {

            hdr.ipv4.dstAddr:exact;

            hdr.udp.dstPort:exact;

        }

        actions = {

            NoAction;

            setflag;

        }

        default_action = NoAction();

    }

 

    table acquire_token {

        actions = {read_token_register;}

    }

 

    apply {

        if (hdr.ethernet.ethernetType == 0x0800) {

               ipv4_lpm.apply();     

               acquire_token.apply();          

           match_ip_udp.apply();

           if (meta.flag==1 && wt_1 >= stdmeta.packet_length) {

                  write_wt_1();

           } else if (meta.flag==1 && wt_1 < stdmeta.packet_length) {

                  drop();

           }

        }

    }

}

 

 

 

control bo_Egress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta)

{  

    apply {

 

    }

}

 

control bo_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.options

        },hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);

    }

}

 

control bo_UpdateChecksum(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.options

        },hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);

    }   

}

 

control bo_Deparser(packet_out packet, in headers hdr) {

    apply {

        packet.emit(hdr.ethernet);

        packet.emit(hdr.ipv4);

        packet.emit(hdr.udp);

    }

 

}

 

V1Switch<headers, metadata>(bo_Parser(), bo_VerifyChecksum(), bo_Ingress(), bo_Egress(), bo_UpdateChecksum(),bo_Deparser()) main;

 

[token_control.py: every 1 second, this program will reset the register (token) to the initial value.

 

import os

import time

 

 

def token_rewrite():

    os.system('sudo /home/vagrant/behavioral-model/targets/simple_switch/simple_switch_CLI --thrift-port=9090 < cmd_rewrite_token.txt')

    time.sleep(1)

 

if __name__ == '__main__':

    while(True):

        token_rewrite()

        print "rewrite one time"

 

[cmd_rewrite_token.txt: 12500 bytes/sec (~100kbps)

register_write rgt 0 12500

 

[Execution]

 

For other connections, the throughput will be near 1Mbps (controlled by the bottleneck link bandwidth).

 

Open another terminal to run token_control.py

 

Re-run the iperf between h1 h2 with port =5555, we can get around 94.1Kbps.

 

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

Department of Computer Science and Information Engineering,

National Quemoy University, Kinmen, Taiwan.