├── README.md ├── ryu_manager.py └── ryu_controller.py /README.md: -------------------------------------------------------------------------------- 1 | OpenFlow DDoS mitigation Ryu controller 2 | ============ 3 | 4 | Developed as a Proof of Concept for my [research](http://rp.delaat.net/2013-2014/p42/report.pdf) on detecting and mitigating DDoS attacks with OpenFLow. The code is a bit messy and I will not be maintaining it. 5 | -------------------------------------------------------------------------------- /ryu_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2014 C. Dillon 4 | # Copyright 2014 M. Berkelaar 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | from ryu.lib import hub 19 | hub.patch(thread=False) 20 | import logging 21 | from ryu import log 22 | log.early_init_log(logging.DEBUG) 23 | from ryu.app import wsgi 24 | from ryu.base.app_manager import AppManager 25 | 26 | 27 | def main(): 28 | log.init_log() 29 | app_mgr = AppManager.get_instance() 30 | app_mgr.load_apps(['./ryu_controller.py']) 31 | 32 | contexts = app_mgr.create_contexts() 33 | services = [] 34 | services.extend(app_mgr.instantiate_apps(**contexts)) 35 | 36 | webapp = wsgi.start_service(app_mgr) 37 | if webapp: 38 | thr = hub.spawn(webapp) 39 | services.append(thr) 40 | 41 | print services 42 | try: 43 | hub.joinall(services) 44 | finally: 45 | app_mgr.close() 46 | 47 | 48 | if __name__ == "__main__": 49 | main() 50 | -------------------------------------------------------------------------------- /ryu_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2014 C. Dillon 4 | # Copyright 2014 M. Berkelaar 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | import eventlet 19 | import numpy 20 | import time 21 | import random 22 | import multiprocessing as mp 23 | import pcap 24 | import dpkt 25 | import socket 26 | import binascii 27 | import operator 28 | import struct 29 | 30 | from ryu.base import app_manager 31 | from ryu.controller import ofp_event 32 | from ryu.controller.handler import MAIN_DISPATCHER 33 | from ryu.controller.handler import HANDSHAKE_DISPATCHER 34 | from ryu.controller.handler import set_ev_cls 35 | from ryu.ofproto import ofproto_v1_0 36 | from ryu.lib.packet import packet as libpacket 37 | from ryu.lib.packet import ethernet 38 | from ryu.lib import hub 39 | from ryu.lib import addrconv 40 | from ryu.lib.mac import haddr_to_bin 41 | from ryu.lib.mac import haddr_to_str 42 | 43 | 44 | unparsed_q = mp.Queue() 45 | parsed_q = eventlet.Queue() 46 | if socket.gethostname() == 'lt-debian': 47 | monitor_port = 65534 48 | dev = 'br0' 49 | elif socket.gethostname() == 'OVS-Switch': 50 | monitor_port = 65534 51 | dev = 'br0' 52 | else: 53 | monitor_port = 34 54 | dev = 'eth1' 55 | 56 | # Set detection mechanism to use. 57 | mode = 6 58 | outgoing_block_mode_v1_dict = {} 59 | outgoing_block_mode_v2_dict = {} 60 | datapath = None 61 | 62 | 63 | def random_int(): 64 | return int(''.join(str(random.choice(range(10))) for _ in range(10))) 65 | 66 | 67 | def ipv4_text_to_int(ip_text): 68 | if ip_text == 0: 69 | return ip_text 70 | assert isinstance(ip_text, str) 71 | return struct.unpack('!I', addrconv.ipv4.text_to_bin(ip_text))[0] 72 | 73 | 74 | class Sniffer(mp.Process): 75 | def __init__(self, q, dev_='eth1'): 76 | super(Sniffer, self).__init__() 77 | self.q = q 78 | self.dev = dev_ 79 | print 'sniffer started' 80 | 81 | def run(self): 82 | p = pcap.pcapObject() 83 | # Get only the first 320 bits of the packet. 84 | p.open_live(self.dev, 320, 1, 0) 85 | 86 | def dump(pktlen, data, timestamp): 87 | self.q.put((pktlen, data, timestamp)) 88 | 89 | try: 90 | while True: 91 | p.dispatch(1, dump) 92 | except KeyboardInterrupt: 93 | pass 94 | print 'PCAP STATS: ' + repr(p.stats()) 95 | 96 | 97 | class Parser(): 98 | def __init__(self, in_q, out_q): 99 | self.in_q = in_q 100 | self.out_q = out_q 101 | 102 | @staticmethod 103 | def parse(pktlen, data, timestamp): 104 | packet = {'len': pktlen, 'time': timestamp} 105 | eth = dpkt.ethernet.Ethernet(data) 106 | ip = eth.data 107 | 108 | def get_mac_addr(_mac): 109 | maclist = [] 110 | for Z in range(12/2): 111 | maclist.append(_mac[Z*2:Z*2+2]) 112 | mac = ":".join(maclist) 113 | return mac 114 | 115 | if isinstance(ip, dpkt.ip.IP): 116 | data = ip.data 117 | packet['type'] = data.__class__.__name__.lower() 118 | packet['ip_src'] = socket.inet_ntoa(ip.src) 119 | packet['ip_dst'] = socket.inet_ntoa(ip.dst) 120 | packet['mac_src'] = get_mac_addr(binascii.hexlify(eth.src)) 121 | packet['mac_dst'] = get_mac_addr(binascii.hexlify(eth.dst)) 122 | else: 123 | return 124 | if isinstance(data, dpkt.tcp.TCP) or isinstance(data, dpkt.udp.UDP): 125 | packet['port_src'] = data.sport 126 | packet['port_dst'] = data.dport 127 | # packet['seq'] = data.seq 128 | # packet['ack'] = data.ack 129 | # packet['flags'] = data.flags 130 | packet['icmp_code'] = "''" 131 | elif isinstance(data, dpkt.icmp.ICMP): 132 | packet['port_src'] = "''" 133 | packet['port_dst'] = "''" 134 | packet['icmp_code'] = data.code 135 | else: 136 | return 137 | return packet 138 | 139 | def run(self): 140 | while True: 141 | eventlet.sleep(0) 142 | try: 143 | item = self.in_q.get(True, 0.05) 144 | if item is None: 145 | continue 146 | parsed = self.parse(item[0], item[1], item[2]) 147 | self.out_q.put(parsed) 148 | except eventlet.queue.Empty: 149 | pass 150 | 151 | 152 | class Counter(): 153 | def __init__(self, q): 154 | self.q = q 155 | self.dst_count = {} 156 | self.src_count = {} 157 | self.ratio_count = {} 158 | self.block_packet_count = {} 159 | self.seqcounter = {} 160 | self.blocked_sources = [] 161 | self.already_blocked = [] 162 | 163 | def create_blocking_flow(self, ip_src): 164 | # This should be reset when the flow has timed out 165 | if not ip_src in self.blocked_sources: 166 | self.blocked_sources.append(ip_src) 167 | match = datapath.ofproto_parser.OFPMatch(dl_type=0x0800, nw_src=ipv4_text_to_int(ip_src), nw_src_mask=32) 168 | mod = datapath.ofproto_parser.OFPFlowMod( 169 | datapath=datapath, match=match, cookie=random_int(), 170 | command=datapath.ofproto.OFPFC_ADD, idle_timeout=10, hard_timeout=0, 171 | priority=0x8000, flags=datapath.ofproto.OFPFF_SEND_FLOW_REM) 172 | datapath.send_msg(mod) 173 | print 'creating blocking flow for source: {0}'.format(ip_src) 174 | 175 | def dst_counter(self, packet): 176 | if packet['ip_dst'] in self.dst_count: 177 | self.dst_count[packet['ip_dst']] += 1 178 | else: 179 | self.dst_count[packet['ip_dst']] = 1 180 | 181 | def src_counter(self, packet): 182 | if packet['ip_src'] in self.src_count: 183 | self.src_count[packet['ip_src']] += 1 184 | if self.src_count[packet['ip_src']] > 10000: 185 | self.create_blocking_flow(packet['ip_src']) 186 | else: 187 | self.src_count[packet['ip_src']] = 1 188 | 189 | def highest_counter(self): 190 | if len(self.dst_count) > 0: 191 | target = max(self.dst_count.iteritems(), key=operator.itemgetter(1)) 192 | print 'target: {0}: {1}'.format(target[0], target[1]) 193 | else: 194 | print 'dst_count empty' 195 | 196 | def ratio_counter(self, packet): 197 | pair_list = sorted([packet['ip_src'], packet['ip_dst']]) 198 | pair = '-'.join(pair_list) 199 | if pair in self.ratio_count: 200 | self.ratio_count[pair][packet['ip_src']] += 1 201 | ratio = float(self.ratio_count[pair][pair_list[0]]) / float(self.ratio_count[pair][pair_list[1]]) 202 | self.ratio_count[pair]['ratio'] = ratio 203 | if ratio > 1000: 204 | self.create_blocking_flow(packet['ip_src']) 205 | else: 206 | self.ratio_count[pair] = {packet['ip_src']: 1, packet['ip_dst']: 1, 'ratio': 1} 207 | 208 | def bad_ratios(self): 209 | ratios = list((k, v['ratio']) for k, v in self.ratio_count.items() if v['ratio'] > 50) 210 | if len(ratios) > 0: 211 | for i in ratios: 212 | print 'Bad ratio: {0}: {1}'.format(i[0].split('-')[0], i[1]) 213 | else: 214 | print 'No bad ratios' 215 | 216 | def outgoing_block_v1(self, packet): 217 | # Outgoing_block_V1 starts sampling from the moment we pushed the block flow. 218 | mac_dst = packet['mac_dst'] 219 | 220 | if mac_dst in outgoing_block_mode_v1_dict: 221 | packet_time = packet['time'] * 1000 # msec 222 | if packet_time > outgoing_block_mode_v1_dict[mac_dst]: 223 | pair_list = sorted([packet['ip_src'], packet['ip_dst']]) 224 | pair = '-'.join(pair_list) 225 | if pair in self.block_packet_count: 226 | if time.time() - self.block_packet_count[pair]['last_update'] > 60: 227 | # These results weren't updated for over 2 seconds. Reset first. 228 | self.block_packet_count[pair][packet['ip_src']] = 1 229 | self.block_packet_count[pair]['last_update'] = time.time() 230 | 231 | self.block_packet_count[pair][packet['ip_src']] += 1 232 | ratio = float(self.block_packet_count[pair][pair_list[0]]) / \ 233 | float(self.block_packet_count[pair][pair_list[1]]) 234 | self.block_packet_count[pair]['ratio'] = ratio 235 | self.block_packet_count[pair]['last_update'] = time.time() 236 | 237 | # If we receive more than X packets while knowing we didn't reply: 238 | if ratio > 10000: 239 | print "blocking ", packet['ip_src'] 240 | self.create_blocking_flow(packet['ip_src']) 241 | 242 | else: 243 | self.block_packet_count[pair] = {packet['ip_src']: 1, packet['ip_dst']: 1, 244 | 'ratio': 1, 'last_update': time.time()} 245 | else: 246 | print "We're not supposed to sample this packet..." 247 | 248 | def outgoing_block_v2(self, packet): 249 | #First sample for X seconds knowing that its non-blocked traffic, 250 | # then sample the packages that we know should been blocked by the outgoing drop flow 251 | # as the higher priority flow mod was dropped by the switch's timeout. 252 | mac_dst = packet['mac_dst'] 253 | if mac_dst in outgoing_block_mode_v2_dict: 254 | packet_time = int(packet['time'] * 1000) # msec 255 | pair_list = sorted([packet['ip_src'], packet['ip_dst']]) 256 | pair = '-'.join(pair_list) 257 | if outgoing_block_mode_v2_dict[mac_dst][0] < packet_time < \ 258 | (outgoing_block_mode_v2_dict[mac_dst][0] + 2000): # 2000 ms 259 | # Dump counters into class dict while keeping note of time, resetting if data is too old. 260 | if pair in self.block_packet_count: 261 | # Reset old data (older than 30 seconds) 262 | if self.block_packet_count[pair]['time_created'] < (time.time() - 30): 263 | self.block_packet_count[pair] = {'packets_before': 1, 'packets_after': 1, 264 | 'time_created': time.time()} 265 | else: 266 | self.block_packet_count[pair]['packets_before'] += 1 267 | else: 268 | self.block_packet_count[pair] = {'packets_before': 1, 'packets_after': 1, 269 | 'time_created': time.time()} 270 | 271 | block_time = outgoing_block_mode_v2_dict[mac_dst][0] + 2000 272 | if packet_time > block_time: 273 | if not pair in self.block_packet_count: 274 | pass 275 | else: 276 | if block_time < packet_time < (block_time + 250): 277 | # First 250 ms after installing block flow is discarded 278 | pass 279 | elif packet_time > block_time and (block_time + 250) < packet_time < (block_time + 750): 280 | self.block_packet_count[pair]['packets_after'] += 1 281 | elif (block_time + 750) < packet_time < (block_time + 1100): 282 | # Last 250 ms is used to trigger the final check to decide wether to 283 | # block the source from this packet or not. 284 | p_before = self.block_packet_count[pair]['packets_before'] 285 | # Check if packet tresholds are met: 286 | if p_before > 1000: 287 | ratio = float(self.block_packet_count[pair]['packets_before']) \ 288 | / float((self.block_packet_count[pair]['packets_after'] * 4)) 289 | else: 290 | # If treshold is not met we will not block. 291 | ratio = 1 292 | if ratio < 5: 293 | ## Switch flood protection 294 | if not packet['ip_src'] in self.already_blocked: 295 | self.create_blocking_flow(packet['ip_src']) 296 | self.already_blocked.append(packet['ip_src']) 297 | print "Created blocking flow for: ", packet['ip_src'] 298 | 299 | def run(self): 300 | while True: 301 | eventlet.sleep(0) 302 | try: 303 | packet = self.q.get(True, 0.05) 304 | if packet is None: 305 | continue 306 | except eventlet.queue.Empty: 307 | continue 308 | 309 | if mode == 1: 310 | self.dst_counter(packet) 311 | elif mode == 2: 312 | self.ratio_counter(packet) 313 | elif mode == 3: 314 | self.dst_counter(packet) 315 | self.ratio_counter(packet) 316 | elif mode == 4: 317 | self.src_counter(packet) 318 | elif mode == 5: 319 | self.outgoing_block_v1(packet) 320 | elif mode == 6: 321 | self.outgoing_block_v2(packet) 322 | 323 | sniffer = Sniffer(unparsed_q, dev) 324 | sniffer.start() 325 | parser = Parser(unparsed_q, parsed_q) 326 | counter = Counter(parsed_q) 327 | 328 | 329 | class RyuController(app_manager.RyuApp): 330 | OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION] 331 | 332 | def __init__(self, *args, **kwargs): 333 | super(RyuController, self).__init__(*args, **kwargs) 334 | self.mac_to_port = {} 335 | self.flowstats = {} 336 | self.detected_flags = {} 337 | self.done = False 338 | self.byte_treshold = 500000 339 | self.packet_treshold = 5000 340 | self.detected_flags = {} 341 | if socket.gethostname() == 'lt-debian': 342 | self.gateway_port = 1 343 | self.map_port_mac = { 344 | 3: '52:54:00:12:e9:0b', 345 | 1: '52:54:00:34:0d:1f', 346 | 65534: '32:32:7c:ac:49:42' 347 | } 348 | elif socket.gethostname() == 'OVS-Switch': 349 | self.gateway_port = 1 350 | self.map_port_mac = { 351 | 1: '52:54:00:ee:6e:d7', 352 | 2: '52:54:00:2d:0f:99', 353 | 65534: '82:c8:6a:fe:33:4f' 354 | } 355 | else: 356 | self.gateway_port = 33 357 | self.map_port_mac = { 358 | 32: '00:0c:29:a7:de:cd', 359 | 33: '00:0a:f7:1a:a9:34', 360 | 34: '00:1b:21:bd:dc:9d' 361 | } 362 | self.gateway_mac = haddr_to_bin(self.map_port_mac[self.gateway_port]) 363 | self.threads.append(hub.spawn(parser.run)) 364 | self.threads.append(hub.spawn(counter.run)) 365 | self.threads.append(hub.spawn(self.poller)) 366 | 367 | @staticmethod 368 | def clear_flows(): 369 | ofproto = datapath.ofproto 370 | 371 | match = datapath.ofproto_parser.OFPMatch() 372 | 373 | mod = datapath.ofproto_parser.OFPFlowMod( 374 | datapath=datapath, match=match, cookie=0, command=ofproto.OFPFC_DELETE, 375 | flags=ofproto.OFPFF_SEND_FLOW_REM) 376 | datapath.send_msg(mod) 377 | 378 | @staticmethod 379 | def add_flow(in_port, dst, actions): 380 | ofproto = datapath.ofproto 381 | 382 | match = datapath.ofproto_parser.OFPMatch( 383 | in_port=in_port, dl_dst=haddr_to_bin(dst)) 384 | 385 | mod = datapath.ofproto_parser.OFPFlowMod( 386 | datapath=datapath, match=match, cookie=0, 387 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, 388 | priority=ofproto.OFP_DEFAULT_PRIORITY, 389 | flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions) 390 | datapath.send_msg(mod) 391 | 392 | print 'MOD' 393 | 394 | def static_flows(self): 395 | ofproto = datapath.ofproto 396 | for k, v in self.map_port_mac.items(): 397 | actions = [datapath.ofproto_parser.OFPActionOutput(k)] 398 | 399 | match = datapath.ofproto_parser.OFPMatch(dl_dst=haddr_to_bin(v)) 400 | mod = datapath.ofproto_parser.OFPFlowMod( 401 | datapath=datapath, match=match, cookie=random_int(), 402 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, 403 | priority=0x4000, 404 | flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions) 405 | datapath.send_msg(mod) 406 | 407 | @staticmethod 408 | def poller(): 409 | while True: 410 | if datapath: 411 | match = datapath.ofproto_parser.OFPMatch(datapath.ofproto.OFPFW_ALL, 412 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 413 | req = datapath.ofproto_parser.OFPFlowStatsRequest(datapath, 0, match, 0xff, 414 | datapath.ofproto.OFPP_NONE) 415 | datapath.send_msg(req) 416 | eventlet.sleep(2) 417 | 418 | # Commented in OpenVswitch to prevent assertion errors. We use static flows. Enable for learning. 419 | # @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) 420 | def _packet_in_handler(self, ev): 421 | msg = ev.msg 422 | ofproto = datapath.ofproto 423 | 424 | pkt = libpacket.Packet(msg.data) 425 | eth = pkt.get_protocol(ethernet.ethernet) 426 | 427 | dst = eth.dst 428 | src = eth.src 429 | 430 | dpid = datapath.id 431 | self.mac_to_port.setdefault(dpid, {}) 432 | 433 | # learn a mac address to avoid FLOOD next time. 434 | self.mac_to_port[dpid][src] = msg.in_port 435 | 436 | if dst in self.mac_to_port[dpid]: 437 | out_port = self.mac_to_port[dpid][dst] 438 | else: 439 | out_port = ofproto.OFPP_FLOOD 440 | # print 'mac_to_port = ' + repr(self.mac_to_port) 441 | 442 | actions = [datapath.ofproto_parser.OFPActionOutput(out_port)] 443 | 444 | # install a flow to avoid packet_in next time 445 | if out_port != ofproto.OFPP_FLOOD: 446 | self.add_flow(msg.in_port, dst, actions) 447 | 448 | out = datapath.ofproto_parser.OFPPacketOut( 449 | datapath=datapath, buffer_id=msg.buffer_id, data=msg.data, 450 | in_port=msg.in_port, actions=actions) 451 | datapath.send_msg(out) 452 | 453 | @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) 454 | def _port_status_handler(self, ev): 455 | msg = ev.msg 456 | reason = msg.reason 457 | port_no = msg.desc.port_no 458 | 459 | ofproto = msg.datapath.ofproto 460 | if reason == ofproto.OFPPR_ADD: 461 | self.logger.info("port added %s", port_no) 462 | elif reason == ofproto.OFPPR_DELETE: 463 | self.logger.info("port deleted %s", port_no) 464 | elif reason == ofproto.OFPPR_MODIFY: 465 | self.logger.info("port modified %s", port_no) 466 | else: 467 | self.logger.info("Illeagal port state %s %s", port_no, reason) 468 | 469 | @set_ev_cls(ofp_event.EventOFPHello, HANDSHAKE_DISPATCHER) 470 | def _hello_reply_handler(self, ev): 471 | msg = ev.msg 472 | global datapath 473 | datapath = msg.datapath 474 | self.clear_flows() 475 | self.static_flows() 476 | 477 | @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) 478 | def _stats_reply_handler(self, ev): 479 | msg = ev.msg 480 | if not msg.datapath == datapath: 481 | print 'ERROR' 482 | return 483 | 484 | for stats in msg.body: 485 | if stats.actions: 486 | if not stats.cookie in self.flowstats: 487 | self.flowstats[stats.cookie] = { 488 | 'packet_count': [stats.packet_count], 489 | 'byte_count': [stats.byte_count] 490 | } 491 | else: 492 | for k in ["byte_count", "packet_count"]: 493 | data = self.flowstats[stats.cookie][k] 494 | if len(data) > 60: 495 | data.pop() 496 | data.reverse() 497 | if k == "byte_count": 498 | data.append(stats.byte_count) 499 | if k == "packet_count": 500 | data.append(stats.packet_count) 501 | data.reverse() 502 | self.flowstats[stats.cookie][k] = data 503 | 504 | check_result_bytes = self.detect(stats.cookie, self.byte_treshold, 505 | self.flowstats[stats.cookie]['byte_count']) 506 | check_result_packets = self.detect(stats.cookie, self.packet_treshold, 507 | self.flowstats[stats.cookie]['packet_count']) 508 | 509 | if check_result_bytes == 1 or check_result_packets == 1: 510 | print 'DDoS detected!!', "bytes / packets:", check_result_bytes, '/', check_result_packets 511 | if monitor_port not in list(i.port for i in stats.actions): 512 | # Temp action mode flag, bypassing the normal sample flow mode for the blockflow mechanism. 513 | if mode == 5: 514 | self.block_out_sample_in(stats) 515 | elif mode == 6: 516 | self.sample_first_block_later(stats) 517 | else: 518 | self.sample_in_out(stats) 519 | 520 | def detect(self, cookie, treshold, data): 521 | difference_list = [] 522 | for i in range(0, (len(data) - 1)): 523 | difference = data[i] - data[i + 1] 524 | difference_list.append(difference) 525 | if len(difference_list) == 0: 526 | return 0 527 | average_difference = sum(difference_list) / len(difference_list) 528 | std = numpy.std(difference_list) 529 | 530 | for item in difference_list: 531 | if item - average_difference > 3 * std and item > treshold and std >= 0: 532 | # print item, " is > then 3 times the standard deviation", std, " item: ", item 533 | if not cookie in self.detected_flags: 534 | self.detected_flags[cookie] = 1 535 | return 1 536 | else: 537 | if self.detected_flags[cookie] == 1: 538 | return 0 539 | elif self.detected_flags[cookie] == 0: 540 | self.detected_flags[cookie] = 1 541 | return 1 542 | else: 543 | return 0 544 | 545 | def block_out_sample_in(self, stats): 546 | ofproto = datapath.ofproto 547 | #SRC BLOCK FLOW 548 | src_mac = stats.match.dl_dst 549 | if not src_mac == self.gateway_mac: 550 | src_match = datapath.ofproto_parser.OFPMatch(dl_src=src_mac, dl_dst=self.gateway_mac) 551 | 552 | src_flow = datapath.ofproto_parser.OFPFlowMod( 553 | datapath=datapath, match=src_match, cookie=random_int(), 554 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=2, 555 | priority=0x6000, 556 | flags=ofproto.OFPFF_SEND_FLOW_REM) 557 | datapath.send_msg(src_flow) 558 | 559 | eventlet.sleep(0.1) 560 | 561 | # DST BLOCK FLOW - We only sample this flow for 1 second, 562 | # Destination does not receive anything. 563 | dst_mac = stats.match.dl_dst 564 | if not dst_mac == self.gateway_mac: 565 | dst_match = datapath.ofproto_parser.OFPMatch(dl_src=self.gateway_mac, dl_dst=dst_mac) 566 | 567 | dst_actions = [datapath.ofproto_parser.OFPActionOutput(monitor_port)] 568 | 569 | dst_flow = datapath.ofproto_parser.OFPFlowMod( 570 | datapath=datapath, match=dst_match, cookie=random_int(), 571 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=1, 572 | priority=0x6000, 573 | flags=ofproto.OFPFF_SEND_FLOW_REM, actions=dst_actions) 574 | datapath.send_msg(dst_flow) 575 | 576 | #Notify parser of this source/destination being blocked from this point on. 577 | outgoing_block_mode_v1_dict[(haddr_to_str(src_mac))] = int(time.time() * 1000) 578 | 579 | def sample_first_block_later(self, stats): 580 | # DST flow with slightly higher priority 581 | ofproto = datapath.ofproto 582 | dst_mac = stats.match.dl_dst 583 | if not dst_mac == self.gateway_mac: 584 | dst_match = datapath.ofproto_parser.OFPMatch(dl_src=self.gateway_mac, dl_dst=dst_mac) 585 | 586 | dst_actions = stats.actions 587 | dst_actions.append(datapath.ofproto_parser.OFPActionOutput(monitor_port)) 588 | 589 | dst_flow = datapath.ofproto_parser.OFPFlowMod( 590 | datapath=datapath, match=dst_match, cookie=random_int(), 591 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=2, 592 | priority=0x6001, 593 | flags=ofproto.OFPFF_SEND_FLOW_REM, actions=dst_actions) 594 | datapath.send_msg(dst_flow) 595 | 596 | outgoing_block_mode_v2_dict[(haddr_to_str(dst_mac))] = [] 597 | # Time of installing first sample flow. 598 | outgoing_block_mode_v2_dict[(haddr_to_str(dst_mac))].append(int(time.time() * 1000)) 599 | 600 | # Not sampling this flow for now. Could check relation between outgoing requests 601 | # and incoming responses if we sampled this. 602 | src_mac = stats.match.dl_dst 603 | if not src_mac == self.gateway_mac: 604 | src_match = datapath.ofproto_parser.OFPMatch(dl_src=src_mac, dl_dst=self.gateway_mac) 605 | 606 | src_actions = [datapath.ofproto_parser.OFPActionOutput(self.gateway_port)] 607 | 608 | src_flow = datapath.ofproto_parser.OFPFlowMod( 609 | datapath=datapath, match=src_match, cookie=random_int(), 610 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=2, 611 | priority=0x6001, 612 | flags=ofproto.OFPFF_SEND_FLOW_REM, actions=src_actions) 613 | datapath.send_msg(src_flow) 614 | 615 | #SRC BLOCK FLOW 616 | src_mac = stats.match.dl_dst 617 | if not src_mac == self.gateway_mac: 618 | src_match = datapath.ofproto_parser.OFPMatch(dl_src=src_mac, dl_dst=self.gateway_mac) 619 | 620 | src_flow = datapath.ofproto_parser.OFPFlowMod( 621 | datapath=datapath, match=src_match, cookie=random_int(), 622 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=3, 623 | priority=0x6000, 624 | flags=ofproto.OFPFF_SEND_FLOW_REM) 625 | datapath.send_msg(src_flow) 626 | 627 | # Time of installing DROP flow. We do not want to count packets after this flow expired 628 | # (3 seconds from this moment on) 629 | outgoing_block_mode_v2_dict[(haddr_to_str(dst_mac))].append(int(time.time() * 1000)) 630 | 631 | # DST BLOCK FLOW - We only sample this flow for 1 second, 632 | # destination does not receive anything. 633 | dst_mac = stats.match.dl_dst 634 | if not dst_mac == self.gateway_mac: 635 | dst_match = datapath.ofproto_parser.OFPMatch(dl_src=self.gateway_mac, dl_dst=dst_mac) 636 | 637 | dst_actions = [datapath.ofproto_parser.OFPActionOutput(monitor_port)] 638 | 639 | dst_flow = datapath.ofproto_parser.OFPFlowMod( 640 | datapath=datapath, match=dst_match, cookie=random_int(), 641 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=3, 642 | priority=0x6000, 643 | flags=ofproto.OFPFF_SEND_FLOW_REM, actions=dst_actions) 644 | datapath.send_msg(dst_flow) 645 | 646 | def sample_in_out(self, stats): 647 | print 'sample_in_out started' 648 | ofproto = datapath.ofproto 649 | # DST flow 650 | dst_mac = stats.match.dl_dst 651 | if not dst_mac == self.gateway_mac: 652 | dst_match = datapath.ofproto_parser.OFPMatch(dl_src=self.gateway_mac, dl_dst=dst_mac) 653 | 654 | dst_actions = stats.actions 655 | dst_actions.append(datapath.ofproto_parser.OFPActionOutput(monitor_port)) 656 | 657 | dst_flow = datapath.ofproto_parser.OFPFlowMod( 658 | datapath=datapath, match=dst_match, cookie=random_int(), 659 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=10, 660 | priority=0x6000, 661 | flags=ofproto.OFPFF_SEND_FLOW_REM, actions=dst_actions) 662 | datapath.send_msg(dst_flow) 663 | print 'Creating dst match sample flow' 664 | 665 | # SRC flow 666 | src_mac = stats.match.dl_dst 667 | if not src_mac == self.gateway_mac: 668 | src_match = datapath.ofproto_parser.OFPMatch(dl_src=src_mac, dl_dst=self.gateway_mac) 669 | 670 | src_actions = [datapath.ofproto_parser.OFPActionOutput(self.gateway_port), 671 | datapath.ofproto_parser.OFPActionOutput(monitor_port)] 672 | 673 | src_flow = datapath.ofproto_parser.OFPFlowMod( 674 | datapath=datapath, match=src_match, cookie=random_int(), 675 | command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=60, 676 | priority=0x6000, 677 | flags=ofproto.OFPFF_SEND_FLOW_REM, actions=src_actions) 678 | datapath.send_msg(src_flow) 679 | print 'Creating src match sample flow' 680 | --------------------------------------------------------------------------------