Adding initial version of the heavy hitter detection

This commit is contained in:
Sean Choi 2016-08-10 14:49:53 -07:00
parent dd4c556be5
commit 846f059ddd
9 changed files with 520 additions and 0 deletions

View File

@ -0,0 +1,11 @@
# Implementing Heavy Hitter Dectection
## Introduction
In this tutorial, we want to detect the heavy hitters on the switch.
Heavy hitters can simply be defined as the IP sources who send unusually large number of traffic.
## Running the starter code
Simply run `./run_demo.sh`, which will fire up a mininet instance of 3 hosts and 1 switch.

View File

@ -0,0 +1,3 @@
sudo mn -c
sudo killall lt-simple_switch
sudo rm -f *.pcap

View File

@ -0,0 +1,15 @@
table_set_default send_frame _drop
table_set_default forward _drop
table_set_default ipv4_lpm _drop
table_set_default set_heavy_hitter_count_table1 set_heavy_hitter_count1
table_set_default set_heavy_hitter_count_table2 set_heavy_hitter_count2
table_set_default set_heavy_hitter_table set_heavy_hitter
table_add send_frame rewrite_mac 1 => 00:00:00:00:00:01
table_add send_frame rewrite_mac 2 => 00:00:00:00:00:02
table_add send_frame rewrite_mac 2 => 00:00:00:00:00:03
table_add forward set_dmac 10.0.0.1 => 00:00:00:00:00:01
table_add forward set_dmac 10.0.0.2 => 00:00:00:00:00:02
table_add forward set_dmac 10.0.0.3 => 00:00:00:00:00:03
table_add ipv4_lpm set_nhop 10.0.0.1/32 => 10.0.0.1 1
table_add ipv4_lpm set_nhop 10.0.0.2/32 => 10.0.0.2 2
table_add ipv4_lpm set_nhop 10.0.0.3/32 => 10.0.0.3 3

View File

@ -0,0 +1,243 @@
/* Copyright 2013-present Barefoot Networks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define HEAVY_HITTER_THRESHOLD 1000
header_type ethernet_t {
fields {
dstAddr : 48;
srcAddr : 48;
etherType : 16;
}
}
header_type ipv4_t {
fields {
version : 4;
ihl : 4;
diffserv : 8;
totalLen : 16;
identification : 16;
flags : 3;
fragOffset : 13;
ttl : 8;
protocol : 8;
hdrChecksum : 16;
srcAddr : 32;
dstAddr: 32;
}
}
parser start {
return parse_ethernet;
}
#define ETHERTYPE_IPV4 0x0800
header ethernet_t ethernet;
parser parse_ethernet {
extract(ethernet);
return select(latest.etherType) {
ETHERTYPE_IPV4 : parse_ipv4;
default: ingress;
}
}
header ipv4_t ipv4;
field_list ipv4_checksum_list {
ipv4.version;
ipv4.ihl;
ipv4.diffserv;
ipv4.totalLen;
ipv4.identification;
ipv4.flags;
ipv4.fragOffset;
ipv4.ttl;
ipv4.protocol;
ipv4.srcAddr;
ipv4.dstAddr;
}
field_list_calculation ipv4_checksum {
input {
ipv4_checksum_list;
}
algorithm : csum16;
output_width : 16;
}
calculated_field ipv4.hdrChecksum {
verify ipv4_checksum;
update ipv4_checksum;
}
parser parse_ipv4 {
extract(ipv4);
return ingress;
}
action _drop() {
drop();
}
header_type custom_metadata_t {
fields {
nhop_ipv4: 32;
// Add metadata for hashes
hash_val1: 16;
hash_val2: 16;
count_val1: 16;
count_val2: 16;
is_heavy_hitter: 8;
}
}
metadata custom_metadata_t custom_metadata;
action set_nhop(nhop_ipv4, port) {
modify_field(custom_metadata.nhop_ipv4, nhop_ipv4);
modify_field(standard_metadata.egress_spec, port);
add_to_field(ipv4.ttl, -1);
}
action set_dmac(dmac) {
modify_field(ethernet.dstAddr, dmac);
}
// Define the field list to compute the hash on
field_list ipv4_hash_fields {
ipv4.srcAddr;
}
// Define two different hash functions to store the counts
field_list_calculation heavy_hitter_hash1 {
input {
ipv4_hash_fields;
}
algorithm : csum16;
output_width : 16;
}
field_list_calculation heavy_hitter_hash2 {
input {
ipv4_hash_fields;
}
algorithm : crc16;
output_width : 16;
}
// Define the registers to store the counts
register heavy_hitter_counter1{
width : 16;
instance_count : 16;
}
register heavy_hitter_counter2{
width : 16;
instance_count : 16;
}
// Actions to set heavy hitter filter
action set_heavy_hitter_count1() {
modify_field_with_hash_based_offset(custom_metadata.hash_val1, 0,
heavy_hitter_hash1, 16);
register_read(custom_metadata.count_val1, heavy_hitter_counter1, custom_metadata.hash_val1);
add_to_field(custom_metadata.count_val1, 1);
register_write(heavy_hitter_counter1, custom_metadata.hash_val1, custom_metadata.count_val1);
}
action set_heavy_hitter_count2() {
modify_field_with_hash_based_offset(custom_metadata.hash_val2, 0,
heavy_hitter_hash2, 16);
register_read(custom_metadata.count_val2, heavy_hitter_counter2, custom_metadata.hash_val2);
add_to_field(custom_metadata.count_val2, 1);
register_write(heavy_hitter_counter2, custom_metadata.hash_val2, custom_metadata.count_val2);
}
// Action to set the heavy hitter metadata indicator
action set_heavy_hitter() {
modify_field(custom_metadata.is_heavy_hitter, 1);
}
// Define the tables to run actions
table set_heavy_hitter_count_table1 {
actions { set_heavy_hitter_count1; }
size: 1;
}
table set_heavy_hitter_count_table2 {
actions { set_heavy_hitter_count2; }
size: 1;
}
// Define table to set the heavy hitter metadata
table set_heavy_hitter_table {
actions { set_heavy_hitter; }
size: 1;
}
table ipv4_lpm {
reads {
ipv4.dstAddr : lpm;
}
actions {
set_nhop;
_drop;
}
size: 1024;
}
table forward {
reads {
custom_metadata.nhop_ipv4 : exact;
}
actions {
set_dmac;
_drop;
}
size: 512;
}
action rewrite_mac(smac) {
modify_field(ethernet.srcAddr, smac);
}
table send_frame {
reads {
standard_metadata.egress_port: exact;
}
actions {
rewrite_mac;
_drop;
}
size: 256;
}
control ingress {
apply(ipv4_lpm);
// Add table control here
apply(set_heavy_hitter_count_table1);
apply(set_heavy_hitter_count_table2);
if (custom_metadata.count_val1 > 1000 and custom_metadata.count_val2 > HEAVY_HITTER_THRESHOLD) {
apply(set_heavy_hitter_table);
}
apply(forward);
}
control egress {
apply(send_frame);
}

View File

@ -0,0 +1,34 @@
#!/usr/bin/python
# Copyright 2013-present Barefoot Networks, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from scapy.all import sniff, sendp
from scapy.all import Packet
from scapy.all import ShortField, IntField, LongField, BitField
import sys
import struct
def handle_pkt(pkt):
pkt = str(pkt)
print pkt
sys.stdout.flush()
def main():
sniff(iface = "eth0",
prn = lambda x: handle_pkt(x))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,33 @@
#!/bin/bash
# Copyright 2013-present Barefoot Networks, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
source $THIS_DIR/../../env.sh
P4C_BM_SCRIPT=$P4C_BM_PATH/p4c_bm/__main__.py
SWITCH_PATH=$BMV2_PATH/targets/simple_switch/simple_switch
CLI_PATH=$BMV2_PATH/tools/runtime_CLI.py
$P4C_BM_SCRIPT p4src/heavy_hitter.p4 --json heavy_hitter.json
# This gives libtool the opportunity to "warm-up"
sudo $SWITCH_PATH >/dev/null 2>&1
sudo PYTHONPATH=$PYTHONPATH:$BMV2_PATH/mininet/ python topo.py \
--behavioral-exe $SWITCH_PATH \
--json heavy_hitter.json \
--cli $CLI_PATH

View File

@ -0,0 +1,47 @@
#!/usr/bin/python
# Copyright 2013-present Barefoot Networks, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from scapy.all import Ether, IP, sendp
import networkx as nx
import sys
def read_topo():
nb_hosts = 0
nb_switches = 0
links = []
with open("topo.txt", "r") as f:
line = f.readline()[:-1]
w, nb_switches = line.split()
assert(w == "switches")
line = f.readline()[:-1]
w, nb_hosts = line.split()
assert(w == "hosts")
for line in f:
if not f: break
a, b = line.split()
links.append( (a, b) )
return int(nb_hosts), int(nb_switches), links
def main():
for i in range(1):
p = Ether(dst="00:00:00:00:00:02")/IP(dst="10.0.0.2")
print p.show()
sendp(p, iface = "eth0")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,129 @@
#!/usr/bin/python
# Copyright 2013-present Barefoot Networks, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from mininet.net import Mininet
from mininet.topo import Topo
from mininet.log import setLogLevel
from mininet.cli import CLI
from p4_mininet import P4Switch, P4Host
import argparse
from time import sleep
import os
import subprocess
_THIS_DIR = os.path.dirname(os.path.realpath(__file__))
_THRIFT_BASE_PORT = 22222
parser = argparse.ArgumentParser(description='Mininet demo')
parser.add_argument('--behavioral-exe', help='Path to behavioral executable',
type=str, action="store", required=True)
parser.add_argument('--json', help='Path to JSON config file',
type=str, action="store", required=True)
parser.add_argument('--cli', help='Path to BM CLI',
type=str, action="store", required=True)
args = parser.parse_args()
class MyTopo(Topo):
def __init__(self, sw_path, json_path, nb_hosts, nb_switches, links, **opts):
# Initialize topology and default options
Topo.__init__(self, **opts)
for i in xrange(nb_switches):
self.addSwitch('s%d' % (i + 1),
sw_path = sw_path,
json_path = json_path,
thrift_port = _THRIFT_BASE_PORT + i,
pcap_dump = True,
device_id = i)
for h in xrange(nb_hosts):
self.addHost('h%d' % (h + 1), ip="10.0.0.%d" % (h + 1),
mac="00:00:00:00:00:0%d" % (h+1))
for a, b in links:
self.addLink(a, b)
def read_topo():
nb_hosts = 0
nb_switches = 0
links = []
with open("topo.txt", "r") as f:
line = f.readline()[:-1]
w, nb_switches = line.split()
assert(w == "switches")
line = f.readline()[:-1]
w, nb_hosts = line.split()
assert(w == "hosts")
for line in f:
if not f: break
a, b = line.split()
links.append( (a, b) )
return int(nb_hosts), int(nb_switches), links
def main():
nb_hosts, nb_switches, links = read_topo()
topo = MyTopo(args.behavioral_exe,
args.json,
nb_hosts, nb_switches, links)
net = Mininet(topo = topo,
host = P4Host,
switch = P4Switch,
controller = None )
net.start()
for n in xrange(nb_hosts):
h = net.get('h%d' % (n + 1))
for off in ["rx", "tx", "sg"]:
cmd = "/sbin/ethtool --offload eth0 %s off" % off
print cmd
h.cmd(cmd)
print "disable ipv6"
h.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
h.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1")
h.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1")
h.cmd("sysctl -w net.ipv4.tcp_congestion_control=reno")
h.cmd("iptables -I OUTPUT -p icmp --icmp-type destination-unreachable -j DROP")
sleep(1)
for i in xrange(nb_switches):
cmd = [args.cli, "--json", args.json,
"--thrift-port", str(_THRIFT_BASE_PORT + i)]
with open("commands.txt", "r") as f:
print " ".join(cmd)
try:
output = subprocess.check_output(cmd, stdin = f)
print output
except subprocess.CalledProcessError as e:
print e
print e.output
sleep(1)
print "Ready !"
CLI( net )
net.stop()
if __name__ == '__main__':
setLogLevel( 'info' )
main()

View File

@ -0,0 +1,5 @@
switches 1
hosts 3
h1 s1
h2 s1
h3 s1