diff --git a/SIGCOMM_2016/heavy_hitter/README.md b/SIGCOMM_2016/heavy_hitter/README.md new file mode 100644 index 0000000..a0de9ec --- /dev/null +++ b/SIGCOMM_2016/heavy_hitter/README.md @@ -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. diff --git a/SIGCOMM_2016/heavy_hitter/cleanup b/SIGCOMM_2016/heavy_hitter/cleanup new file mode 100755 index 0000000..c0a8bb6 --- /dev/null +++ b/SIGCOMM_2016/heavy_hitter/cleanup @@ -0,0 +1,3 @@ +sudo mn -c +sudo killall lt-simple_switch +sudo rm -f *.pcap diff --git a/SIGCOMM_2016/heavy_hitter/commands.txt b/SIGCOMM_2016/heavy_hitter/commands.txt new file mode 100644 index 0000000..0454ebb --- /dev/null +++ b/SIGCOMM_2016/heavy_hitter/commands.txt @@ -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 diff --git a/SIGCOMM_2016/heavy_hitter/p4src/heavy_hitter.p4 b/SIGCOMM_2016/heavy_hitter/p4src/heavy_hitter.p4 new file mode 100644 index 0000000..7bcd29b --- /dev/null +++ b/SIGCOMM_2016/heavy_hitter/p4src/heavy_hitter.p4 @@ -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); +} diff --git a/SIGCOMM_2016/heavy_hitter/receive.py b/SIGCOMM_2016/heavy_hitter/receive.py new file mode 100755 index 0000000..4c0e575 --- /dev/null +++ b/SIGCOMM_2016/heavy_hitter/receive.py @@ -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() diff --git a/SIGCOMM_2016/heavy_hitter/run_demo.sh b/SIGCOMM_2016/heavy_hitter/run_demo.sh new file mode 100755 index 0000000..53e0062 --- /dev/null +++ b/SIGCOMM_2016/heavy_hitter/run_demo.sh @@ -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 diff --git a/SIGCOMM_2016/heavy_hitter/send.py b/SIGCOMM_2016/heavy_hitter/send.py new file mode 100755 index 0000000..9be5c4a --- /dev/null +++ b/SIGCOMM_2016/heavy_hitter/send.py @@ -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() diff --git a/SIGCOMM_2016/heavy_hitter/topo.py b/SIGCOMM_2016/heavy_hitter/topo.py new file mode 100644 index 0000000..1bff8f4 --- /dev/null +++ b/SIGCOMM_2016/heavy_hitter/topo.py @@ -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() diff --git a/SIGCOMM_2016/heavy_hitter/topo.txt b/SIGCOMM_2016/heavy_hitter/topo.txt new file mode 100644 index 0000000..42b6aa2 --- /dev/null +++ b/SIGCOMM_2016/heavy_hitter/topo.txt @@ -0,0 +1,5 @@ +switches 1 +hosts 3 +h1 s1 +h2 s1 +h3 s1