ddos-mlp-mitigation

git clone git://git.codymlewis.com/ddos-mlp-mitigation.git
Log | Files | Refs | Submodules | README | LICENSE

commit 4043dd8700b82b78f640cb4975e065a8e5cd56f0
parent ae16e36698f22610666bb0bc4bd30ff707278b15
Author: Cody Lewis <cody@codymlewis.com>
Date:   Tue,  7 Apr 2020 08:24:25 +1000

Added detection mechanism

Diffstat:
M.gitignore | 5+++++
Mcreate_network | 68+++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Anetwork_controller.py | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 293 insertions(+), 13 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -2,3 +2,8 @@ __pycache__/ *.pyc tags Session.vim +pox/ +pox/* +pox.py +model.h5 +training_data.txt diff --git a/create_network b/create_network @@ -5,8 +5,11 @@ Create a mininet based network for the botnet ''' +import sys import time +import numpy as np + from mininet.topo import Topo from mininet.net import Mininet from mininet.node import Controller, Node, OVSSwitch, RemoteController @@ -70,19 +73,58 @@ def run_network(num_bots): controller=RemoteController ) net.start() - info("*** Starting web server on target\n") - net['t0'].cmdPrint("export FLASK_APP=WebServer.py && flask run --host=0.0.0.0 &") - time.sleep(1) - info("*** User browsing web service\n") - net['u0'].cmdPrint(f"netsurf http://{net['t0'].IP()}:5000/ &") - time.sleep(1) - info("*** Starting botnet controller\n") - net['b0'].cmdPrint(f"./botnet_controller -n {num_bots} -t {net['t0'].IP()} &") - time.sleep(1) - info("*** Starting botnet attack on the target") - for i in range(1, num_bots + 1): - net[f"b{i}"].cmdPrint(f"./bot -c {net['b0'].IP()} &") - CLI(net) + finish_time = time.time() + 3000 + if "--train" in sys.argv: + finish_time = time.time() + 3000 + for host in net.hosts: + host.cmdPrint("export FLASK_APP=WebServer.py && flask run --host=0.0.0.0 &") + time.sleep(3) + if "--attack" in sys.argv: + info("*** Starting web server on target\n") + net['t0'].cmdPrint("export FLASK_APP=WebServer.py && flask run --host=0.0.0.0 &") + time.sleep(1) + info("*** User browsing web service\n") + net['u0'].cmdPrint(f"netsurf http://{net['t0'].IP()}:5000/ &") + time.sleep(1) + info("*** Starting botnet controller\n") + net['b0'].cmdPrint(f"./botnet_controller -n {num_bots} -t {net['t0'].IP()} &") + time.sleep(1) + info("*** Starting botnet attack on the target\n") + for i in range(1, num_bots + 1): + net[f"b{i}"].cmdPrint(f"./bot -c {net['b0'].IP()} &") + elif "--all-attack" in sys.argv: + info("*** Everyone attacking the target\n") + net['b0'].cmdPrint(f"./botnet_controller -n {len(net.hosts) - 1} -t {net['t0'].IP()} &") + for host in net.hosts: + if host is not net['b0']: + host.cmd(f"./bot -c {net['b0'].IP()} &") + while time.time() < finish_time: + time.sleep(1) + elif "--normal" in sys.argv: + info("*** Normal activity\n") + while time.time() < finish_time: + host = net.hosts[ + int(np.round(np.random.uniform(len(net.hosts)))) - 1 + ] + random_host_ip = net.hosts[ + int(np.round(np.random.uniform(len(net.hosts)))) - 1 + ].IP() + if np.random.choice(range(1, 100)) < 95: + info("*** TCP activity\n") + host.cmd(f"curl {random_host_ip}:5000") + else: + info("*** ICMP activity\n") + host.cmd(f"ping -c1 {random_host_ip}") + time.sleep(np.random.uniform(0, 1)) + else: + info("*** Starting web server on target\n") + net['t0'].cmdPrint("export FLASK_APP=WebServer.py && flask run --host=0.0.0.0 &") + time.sleep(1) + info("*** User browsing web service\n") + net['u0'].cmdPrint(f"netsurf http://{net['t0'].IP()}:5000/ &") + time.sleep(1) + if "--cli" in sys.argv: + CLI(net) net.stop() if __name__ == '__main__': diff --git a/network_controller.py b/network_controller.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +''' +The controller of the network. +''' + +import sys +import time + +import tensorflow as tf +from tensorflow import keras + +import numpy as np +from minisom import MiniSom + +import pox.lib.packet as pac +from pox.boot import boot +from pox.core import core +from pox.lib.recoco import Timer + +import pox.openflow.libopenflow_01 as of + + +if __name__ != "__main__": + import pox.forwarding.l2_learning as l2l + LOG = core.getLogger() + +IPV4_PROTOCOLS = { + pac.ipv4.ICMP_PROTOCOL: "ICMP", + pac.ipv4.IGMP_PROTOCOL: "IGMP", + pac.ipv4.TCP_PROTOCOL: "TCP", + pac.ipv4.UDP_PROTOCOL: "UDP", +} + +IPV6_PROTOCOLS = { + pac.ipv6.ICMP6_PROTOCOL: "ICMP", + pac.ipv6.IGMP_PROTOCOL: "IGMP", + pac.ipv6.TCP_PROTOCOL: "TCP", + pac.ipv6.UDP_PROTOCOL: "UDP", +} + +class Flow: + def __init__(self, src, dst, comm_prot, packets, amount_bytes): + self.src = src + self.dst = dst + self.comm_prot = comm_prot + self.packets = packets + self.bytes = amount_bytes + self.time_created = time.time() + self.time_last_used = time.time() + + def __str__(self): + return "{} -> {}: {}".format(self.src, self.dst, self.comm_prot) + + def is_pair(self, other): + p = self.src == other.dst + q = self.dst == other.src + v = self.comm_prot == other.comm_prot + return p and q and v + + def __eq__(self, other): + if isinstance(other, Flow): + p = self.src == other.src + q = self.dst == other.dst + v = self.comm_prot == other.comm_prot + return p and q and v + return False + + def update(self, packets, amount_bytes): + self.time_last_used = time.time() + self.packets += packets + self.bytes += amount_bytes + +class Controller(object): + def __init__(self, connection, gen_data, label, detect, interval=5.0, clean_interval=30): + self.connection = connection + connection.addListeners(self) + self.label = label + self.mac_to_port = {} + self.flows = dict() + self.growing_flows = dict() + self.ports = set() + self.growing_ports = set() + self.time_started = time.time() + self.interval = interval + if gen_data: + self.data_timer = Timer(interval, self.write_data, recurring=True) + self.growth_timer = Timer(interval, self.detect_growth, recurring=True) + self.clean_interval = clean_interval + self.clean_timer = Timer(clean_interval, self.clean_flows, recurring=True) + self.detect = detect + if detect: + self.model = keras.models.load_model('model.h5') + + def resend_packet(self, packet_in, out_port): + msg = of.ofp_packet_out() + msg.data = packet_in + action = of.ofp_action_output(port=out_port) + msg.actions.append(action) + self.connection.send(msg) + + def act_like_switch(self, packet, packet_in): + if self.detect: + prediction = np.round(self.model.predict([self.calc_tuple()])[0][0]) + LOG.debug("Prediction: %s", prediction) + if prediction == 1.0: + LOG.debug("Attack detected!") + return + pl = packet.payload + if isinstance(pl, pac.arp): + src = pl.protosrc + dst = pl.protodst + comm_prot = "ARP" + else: + src = pl.srcip + dst = pl.dstip + if isinstance(pl, pac.ipv4): + comm_prot = IPV4_PROTOCOLS[pl.protocol] + else: + comm_prot = "IPV6" + flow = Flow(src, dst, comm_prot, 1, len(pl)) + flow_name = str(flow) + if self.flows.get(flow_name): + self.flows[flow_name].update(1, len(pl)) + else: + self.flows[flow_name] = flow + self.growing_flows[flow_name] = flow + if len(packet_in.data) == packet_in.total_len: + self.mac_to_port[packet.src] = packet_in.in_port + self.ports = self.ports.union([packet_in.in_port]) + self.growing_ports = self.growing_ports.union([packet_in.in_port]) + if self.mac_to_port.get(packet.dst): + self.resend_packet(packet_in, self.mac_to_port[packet.dst]) + else: + self.resend_packet(packet_in, of.OFPP_ALL) + + def calc_tuple(self): + interval = time.time() - self.time_started + amount_packets = [] + amount_bytes = [] + durations = [] + current_time = time.time() + num_pair_flows = float(0) + all_flows = self.flows.values() + num_flows = float(len(all_flows)) + for i, flow in enumerate(all_flows): + amount_packets.append(flow.packets) + amount_bytes.append(flow.bytes) + durations.append(current_time - flow.time_created) + for other_flow in all_flows[i + 1:]: + if flow.is_pair(other_flow): + num_pair_flows += 1 + all_growing_flows = self.growing_flows.values() + num_growing_flows = len(all_growing_flows) + num_growing_pair_flows = 0 + for i, flow in enumerate(all_growing_flows): + for other_flow in all_growing_flows[i + 1:]: + if flow.is_pair(other_flow): + num_growing_pair_flows += 1 + return [ + np.median(amount_packets) if len(amount_packets) else 0, + np.median(amount_bytes) if len(amount_bytes) else 0, + np.median(durations) if len(amount_bytes) else 0, + ((2 * num_pair_flows) / num_flows) if num_flows > 0 else 0, + (num_growing_flows - (2 * num_growing_pair_flows) / self.interval), + len(self.growing_ports) / self.interval, + ] + + def detect_growth(self): + ''' + Reset variables for detecting growth of them + ''' + self.growing_flows = dict() + self.growing_ports = set() + + def write_data(self): + six_tuple = self.calc_tuple() + six_tuple.append(self.label) + LOG.debug("Writing some training data") + LOG.debug("Current tuple: %s", six_tuple) + with open("training_data.txt", "a") as f: + f.write(" ".join(map(str, six_tuple)) + "\n") + LOG.debug("Written.") + + def clean_flows(self): + current_time = time.time() + del_indices = [] + for flow in self.flows.values(): + if (current_time - flow.time_last_used) > self.clean_interval: + del_indices.append(str(flow)) + for del_index in del_indices: + del self.flows[del_index] + + def _handle_PacketIn(self, event): + packet = event.parsed + if not packet.parsed: + LOG.warning("Ignoring incomplete packet") + else: + packet_in = event.ofp + self.act_like_switch(packet, packet_in) + + +def launch(): + def start_switch(event): + LOG.debug("Controlling %s with this", (event.connection,)) + Controller( + event.connection, + "--gen-data" in sys.argv, + 1 if "--attack" in sys.argv else 0, + "--detect" in sys.argv + ) + core.openflow.addListenerByName("ConnectionUp", start_switch) + +if __name__ == '__main__': + if "--train" in sys.argv: + data, bin_labels = (lambda x: (x[:, :6], x[:, 6]))(np.loadtxt("training_data.txt")) + labels = np.array([[1, 0] if l == 0 else [0, 1] for l in bin_labels]) + inputs = keras.Input(shape=(6,)) + x = keras.layers.Dense(10, activation=tf.nn.relu)(inputs) + x = keras.layers.Dense(10, activation=tf.nn.relu)(x) + outputs = keras.layers.Dense(2, activation=tf.nn.softmax)(x) + model = keras.Model(inputs=inputs, outputs=outputs) + model.compile( + optimizer="RMSProp", + loss=keras.losses.CategoricalCrossentropy() + ) + history = model.fit(x=data, y=labels, epochs=500, verbose=1) + print("Reached loss: {}".format(history.history['loss'][-1])) + model.save("model.h5") + print("Saved model as model.h5") + else: + boot(["log.level", "--DEBUG", "network_controller"])