From ee2de0decf8a4df017d7ebe1c326ceb585c999da Mon Sep 17 00:00:00 2001 From: Zloooy Date: Sun, 27 Jan 2019 23:28:58 +0300 Subject: [PATCH 01/10] Added some abstract parent classes and folders, made tests for parser and IpGenerator, fixed some bugs in parsing. --- CoreModel.py | 17 ---- MainPresenter.py | 81 +++++++++++-------- Parser.py | 21 ----- __init__.py | 0 .../AbstractAddressGenerator.py | 9 +++ address_generation/AbstractParser.py | 14 ++++ address_generation/IpGenerator.py | 18 +++++ address_generation/Parser.py | 80 ++++++++++++++++++ address_generation/__init__.py | 7 ++ address_generation/test_IpGenerator.py | 21 +++++ address_generation/test_Parser.py | 37 +++++++++ main.py | 14 ++-- network_scan/AbstractScanner.py | 14 ++++ network_scan/CoreModel.py | 15 ++++ network_scan/__init__.py | 7 ++ 15 files changed, 279 insertions(+), 76 deletions(-) delete mode 100644 CoreModel.py delete mode 100644 Parser.py create mode 100644 __init__.py create mode 100644 address_generation/AbstractAddressGenerator.py create mode 100644 address_generation/AbstractParser.py create mode 100644 address_generation/IpGenerator.py create mode 100644 address_generation/Parser.py create mode 100644 address_generation/__init__.py create mode 100644 address_generation/test_IpGenerator.py create mode 100644 address_generation/test_Parser.py create mode 100644 network_scan/AbstractScanner.py create mode 100644 network_scan/CoreModel.py create mode 100644 network_scan/__init__.py diff --git a/CoreModel.py b/CoreModel.py deleted file mode 100644 index eb37824..0000000 --- a/CoreModel.py +++ /dev/null @@ -1,17 +0,0 @@ -import socket - - -class CoreModel: - def __init__(self, timeout): - self.defSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.defSocket.settimeout(int(timeout)) - - def scanIP(self, host, ports): - openPorts = [] - for i in ports[0]: - result = self.defSocket.connect_ex((host, int(i))) - if result == 0: - openPorts.append(i) - self.defSocket.close() - self.defSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - return openPorts diff --git a/MainPresenter.py b/MainPresenter.py index 3003e97..8e366d3 100644 --- a/MainPresenter.py +++ b/MainPresenter.py @@ -1,10 +1,11 @@ -import CoreModel -import Parser +from network_scan.CoreModel import CoreModel +from address_generation.Parser import Parser import threading import queue import datetime from PyQt5.Qt import * from netaddr import IPNetwork +from address_generation.IpGenerator import IpGenerator class MainPresenter: @@ -12,39 +13,48 @@ class MainPresenter: self.ui = ui self.threads = [] self.isScanEnabled = False - self.queue = queue.Queue() + self.parser=Parser() def startScan(self, ipRanges, portsStr, threadNumber, timeout): if timeout == '': timeout = '3' - cidrIPRanges = Parser.getCIDRFromRanges(ipRanges) - ports = Parser.getPortsFromString(portsStr) - ips = [] - for cidr in cidrIPRanges[0]: - for ip in IPNetwork(cidr): - ips.append(str(ip)) - for ip in ips: - self.queue.put(ip) + addresses = self.parser.parse_address_field(ipRanges) + ports = self.parser.parse_port_field(portsStr) + self.ip_generator = IpGenerator(addresses,ports) for i in range(int(threadNumber)): - self.threads.append(ScanThread(self.queue, ports, timeout, self)) + self.threads.append(ScanThread(self.ip_generator, ports, timeout, self)) self.setCurrentThreadsLabel(len(self.threads)) for thread in self.threads: thread.signal.connect(self.setLogText) thread.exit_signal.connect(self.on_thread_exit) thread.start() - def on_thread_exit(self, thread): - self.threads.pop(self.threads.index(thread[0])) - self.setCurrentThreadsLabel(len(self.threads)) - if len(self.threads) == 0: + def on_thread_exit(self, is_last): + if is_last: self.isScanEnabled = False self.ui.startButton.setText("Start") + return + count = 0 + for thr in self.threads: + if thr.is_running: + count = count + 1 + self.setCurrentThreadsLabel(count) def stopScan(self): self.isScanEnabled = False for thread in self.threads: - thread.exit_signal.emit(self.threads.index(thread)) thread.exit() + thread.is_running = False + count = 0 + is_last_thread = False + for i in self.threads: + if not i.is_running: + count += 1 + if count == len(self.threads): + is_last_thread = True + thread.exit_signal.emit(is_last_thread) + self.threads.clear() + self.ui.currentThreadsLabel.setText("0") self.queue = queue.Queue() def setLogText(self, string): @@ -57,27 +67,34 @@ class MainPresenter: class ScanThread(QThread): signal = pyqtSignal(str) - exit_signal = pyqtSignal(list) # This signal for correct pop'ing from thread list + exit_signal = pyqtSignal(bool) - def __init__(self, scanQueue, ports, timeout, presenter, parent=None): + def __init__(self, ip_generator, ports, timeout, presenter, parent=None): QThread.__init__(self, parent) - self.scanQueue = scanQueue - self.coreModel = CoreModel.CoreModel(timeout) - self.ports = ports + self.ip_generator = ip_generator + self.coreModel = CoreModel(timeout) self._stop_event = threading.Event() self.timeout = timeout self.presenter = presenter + self.is_running = True def run(self): while True: - if self.scanQueue.empty(): - self.exit_signal.emit([self]) - self.exit(1) - hostObject = self.scanQueue.get() - open_ports = self.coreModel.scanIP(str(hostObject), self.ports) - signalStr = ''.join(str(x) for x in open_ports) - if(signalStr == ''): - self.signal.emit(str(hostObject) + ' has no open ports!') + scan_address = self.ip_generator.get_next_address(None) + if not scan_address: + break + scan_result = self.coreModel.scan_address(scan_address) + if scan_result==0: + self.signal.emit( '%s has open port: %s' % scan_address) else: - self.signal.emit(str(hostObject) + ' has open ports: ' + signalStr) - self.scanQueue.task_done() + self.signal.emit( '%s has closed port: %s' % scan_address) + self.is_running = False + count = 0 + is_last_thread = False + for i in self.presenter.threads: + if not i.isRunning(): + count += 1 + if count == len(self.presenter.threads): + is_last_thread = True + self.exit_signal.emit(is_last_thread) + self.exit(1) diff --git a/Parser.py b/Parser.py deleted file mode 100644 index e106048..0000000 --- a/Parser.py +++ /dev/null @@ -1,21 +0,0 @@ -import netaddr - - -def getCIDRFromRanges(str_ranges): - str_ranges = str_ranges.replace(' ', '') - ranges = [] - ips = [] - splitted_ranges = str_ranges.split(",") - for i in splitted_ranges: - ranges.append(i.split("-")) - for i in ranges: - if len(ranges[ranges.index(i)]) == 1: - ips.append(netaddr.iprange_to_cidrs(i[0], i[0])) - else: - ips.append(netaddr.iprange_to_cidrs(i[0], i[1])) - return ips - - -def getPortsFromString(str_ports): - str_ports = str_ports.replace(" ", "") - return [str_ports.split(",")] diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/address_generation/AbstractAddressGenerator.py b/address_generation/AbstractAddressGenerator.py new file mode 100644 index 0000000..ea1ead1 --- /dev/null +++ b/address_generation/AbstractAddressGenerator.py @@ -0,0 +1,9 @@ +from abc import abstractmethod, ABC + + +class AbstractAddressGenerator(ABC): + + @classmethod + @abstractmethod + def get_next_address(self, previous_address, **kwargs): + pass diff --git a/address_generation/AbstractParser.py b/address_generation/AbstractParser.py new file mode 100644 index 0000000..36bd85f --- /dev/null +++ b/address_generation/AbstractParser.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod + + +class AbstractParser(ABC): + + @classmethod + @abstractmethod + def parse_address_field(self, field): + pass + + @classmethod + @abstractmethod + def parse_port_field(self, field): + pass diff --git a/address_generation/IpGenerator.py b/address_generation/IpGenerator.py new file mode 100644 index 0000000..ceeed82 --- /dev/null +++ b/address_generation/IpGenerator.py @@ -0,0 +1,18 @@ +from AbstractAddressGenerator import AbstractAddressGenerator +from threading import RLock + +class IpGenerator(AbstractAddressGenerator): + + def __init__(self, address_generator, ports): + self.address_generator = address_generator + self.ports = ports + self.portnum = -1 + self.lock = RLock() + + def get_next_address(self, previous_address): + with self.lock: + self.portnum = (self.portnum + 1) % len(self.ports) + try: + return (str(next(self.address_generator)), self.ports[self.portnum]) + except StopIteration: + return None diff --git a/address_generation/Parser.py b/address_generation/Parser.py new file mode 100644 index 0000000..bf1a079 --- /dev/null +++ b/address_generation/Parser.py @@ -0,0 +1,80 @@ +import ipaddress +from AbstractParser import AbstractParser + + +class Parser(AbstractParser): + + def parse_port_field(self, ports): + """ + Parses ports from string, returns them as integers in the list. + Handles non-existent ports and non-port values. + """ + if ports: + # Using set to avoid repetitions + parsed = set() + ports = ports.split(",") + for port in ports: + try: + # Input is in range form ("100-200"): + if '-' in port: + start, end = map(int, port.split('-')) + parsed.update( + [p for p in range(start, end + 1) if 65355 >= p > 0]) + # Input is a single port ("80"): + else: + parsed.add(int(port)) + except ValueError as e: + # If we get any not integer just ignore it + pass + return sorted(list(parsed)) + else: + # Change to default ports from constant + return [21, 22, 23, 25, 80, 443, 110, 111, 135, 139, 445, 8080, 8443, 53, 143, 989, 990, 3306, 1080, 5554, 6667, 2222, 4444, 666, 6666, 1337, 2020, 31337] + + def parse_address_field(self, ips): + """ + Parses ip input string, returns the generator over them. + + Supports next inputs: + 1) 1.2.3.4 + 2) 192.168.0.0/24 + 3) 1.2.3.4 - 5.6.7.8 + Any non-ip value will be ignored. + """ + + # A set to contain non repeating ip objects from ipaddress + ip_objects = set() + inputs = [ip.strip() for ip in ips.split(',')] + + for input_ in inputs: + try: + # Input is in range form ("1.2.3.4 - 5.6.7.8"): + if '-' in input_: + input_ips = input_.split('-') + ranges = set( + ipaddress.summarize_address_range( + *map(lambda x: ipaddress.IPv4Address(x.strip()), input_ips) + ) + ) + ip_objects.update(ranges) + # Input is in CIDR form ("192.168.0.0/24"): + elif '/' in input_: + network = ipaddress.ip_network(input_) + ip_objects.add(network) + # Input is a single ip ("1.1.1.1"): + else: + ip = ipaddress.ip_address(input_) + ip_objects.add(ip) + except ValueError as e: + print(e) + # If we get any non-ip value just ignore it + pass + + for ip_obj in ip_objects: + # The object is just one ip, simply yield it: + if isinstance(ip_obj, ipaddress.IPv4Address): + yield ip_obj + # The object is a network, yield every host in it: + else: + for host in ip_obj: + yield host diff --git a/address_generation/__init__.py b/address_generation/__init__.py new file mode 100644 index 0000000..f882043 --- /dev/null +++ b/address_generation/__init__.py @@ -0,0 +1,7 @@ +import sys +import os + + +fil = __file__[:__file__.rindex(os.sep)] +print(fil) +sys.path.insert(0,fil) diff --git a/address_generation/test_IpGenerator.py b/address_generation/test_IpGenerator.py new file mode 100644 index 0000000..0e6c296 --- /dev/null +++ b/address_generation/test_IpGenerator.py @@ -0,0 +1,21 @@ +from unittest import TestCase, main +from ipaddress import IPv4Address +from IpGenerator import IpGenerator +from Parser import Parser + + +class testIpGenerator(TestCase): + + def setUp(self): + p = Parser() + self.ipgen = IpGenerator(p.parse_address_field("192.168.1.1"), [80]) + + def testIpGeneration(self): + self.assertEquals( + self.ipgen.get_next_address(None), + (IPv4Address("192.168.1.1"), 80)) + print(self.ipgen.get_next_address(None)) + + +if __name__ == "__main__": + main() diff --git a/address_generation/test_Parser.py b/address_generation/test_Parser.py new file mode 100644 index 0000000..9ca9456 --- /dev/null +++ b/address_generation/test_Parser.py @@ -0,0 +1,37 @@ +from unittest import main, TestCase +from Parser import Parser +import ipaddress + + +class TestParser(TestCase): + + def setUp(self): + self.parser = Parser() + + def test_port_parsing(self): + self.assertEqual(self.parser.parse_port_field("80,90"), [80, 90]) + + def test_ip_parsing(self): + self.assertEqual( + set(self.parser.parse_address_field("192.168.1.1 - 192.168.1.3")), + {ipaddress.IPv4Address(ip) for ip in + [ + "192.168.1.1", + "192.168.1.2", + "192.168.1.3" + ]}) + self.assertEqual( + set(self.parser.parse_address_field("192.168.1.1")), + {ipaddress.IPv4Address("192.168.1.1")} + ) + self.assertEqual( + set(self.parser.parse_address_field("192.168.1.0/31")), + {ipaddress.IPv4Address(ip) for ip in + [ + '192.168.1.1', + '192.168.1.0' + ]}) + + +if __name__ == "__main__": + main() diff --git a/main.py b/main.py index 0015859..455abd3 100644 --- a/main.py +++ b/main.py @@ -19,15 +19,17 @@ class MyWin(QtWidgets.QMainWindow): self.isScanActive = False def startButtonClicked(self): - if self.isScanActive == False: - self.presenter.isScanEnabled = True - self.ui.startButton.setText("Stop") - self.presenter.startScan(self.ui.ipLine.text(), self.ui.portsLine.text(), self.ui.threadsLine.text(), - self.ui.timeoutLine.text()) - else: + if self.presenter.isScanEnabled: self.presenter.isScanEnabled = False self.ui.startButton.setText("Start") self.presenter.stopScan() + else: + self.presenter.isScanEnabled = True + self.ui.startButton.setText("Stop") + self.presenter.startScan(self.ui.ipLine.text(), + self.ui.portsLine.text(), + self.ui.threadsLine.text(), + self.ui.timeoutLine.text()) if __name__ == "__main__": diff --git a/network_scan/AbstractScanner.py b/network_scan/AbstractScanner.py new file mode 100644 index 0000000..53d11ae --- /dev/null +++ b/network_scan/AbstractScanner.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod + + +class AbstractScanner(ABC): + + @classmethod + @abstractmethod + def __init__(self, **kwargs): + pass + + @classmethod + @abstractmethod + def scan_address(self, address): + pass diff --git a/network_scan/CoreModel.py b/network_scan/CoreModel.py new file mode 100644 index 0000000..b1bcd8e --- /dev/null +++ b/network_scan/CoreModel.py @@ -0,0 +1,15 @@ +import socket +from AbstractScanner import AbstractScanner + + +class CoreModel(AbstractScanner): + def __init__(self, timeout): + self.defSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.defSocket.settimeout(int(timeout)) + + def scan_address(self, address): + host, port = address + result = self.defSocket.connect_ex((host, port)) + self.defSocket.close() + self.defSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + return result diff --git a/network_scan/__init__.py b/network_scan/__init__.py new file mode 100644 index 0000000..f882043 --- /dev/null +++ b/network_scan/__init__.py @@ -0,0 +1,7 @@ +import sys +import os + + +fil = __file__[:__file__.rindex(os.sep)] +print(fil) +sys.path.insert(0,fil) From acbfb6607b2270cdf70976a2dbe291a039bd9066 Mon Sep 17 00:00:00 2001 From: Zloooy Date: Mon, 28 Jan 2019 21:45:21 +0300 Subject: [PATCH 02/10] Added JSONStorage to keep results, fixed trouble with IpGenerator --- storage/AbstractStorage.py | 14 ++++++++++++++ storage/JSONStorage.py | 23 +++++++++++++++++++++++ storage/__init__.py | 6 ++++++ 3 files changed, 43 insertions(+) create mode 100644 storage/AbstractStorage.py create mode 100644 storage/JSONStorage.py create mode 100644 storage/__init__.py diff --git a/storage/AbstractStorage.py b/storage/AbstractStorage.py new file mode 100644 index 0000000..3768890 --- /dev/null +++ b/storage/AbstractStorage.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod + + +class AbstractStorage(ABC): + + @classmethod + @abstractmethod + def put_responce(self, address, responce): + pass + + @classmethod + @abstractmethod + def save(self): + pass diff --git a/storage/JSONStorage.py b/storage/JSONStorage.py new file mode 100644 index 0000000..3fa9918 --- /dev/null +++ b/storage/JSONStorage.py @@ -0,0 +1,23 @@ +from AbstractStorage import AbstractStorage +import json +from threading import RLock + + +class JSONStorage(AbstractStorage): + + def __init__(self, path): + self.path = path + self.respdict = dict() + self.lock = RLock() + + def put_responce(self, address, responce): + ip, port = address + if ip not in self.respdict.keys(): + self.respdict[ip] = {"open": [], "close": []} + self.respdict[ip]["open" if responce != 0 else "close"].append(port) + + def save(self): + print("saving") + with open(self.path, "w") as f: + json.dump(self.respdict, f) + self.respdict = {} diff --git a/storage/__init__.py b/storage/__init__.py new file mode 100644 index 0000000..d98799d --- /dev/null +++ b/storage/__init__.py @@ -0,0 +1,6 @@ +import sys +import os + + +fil = __file__[:__file__.rindex(os.sep)] +sys.path.insert(0,fil) From aa3c795f9a0ce7566a224b9cd296c0f03b47e390 Mon Sep 17 00:00:00 2001 From: Zloooy Date: Mon, 28 Jan 2019 21:57:07 +0300 Subject: [PATCH 03/10] Fixed trouble with IpGenerator --- MainPresenter.py | 19 ++++++++++++------- address_generation/IpGenerator.py | 26 ++++++++++++++++++-------- address_generation/__init__.py | 1 - address_generation/test_IpGenerator.py | 14 ++++++++++---- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/MainPresenter.py b/MainPresenter.py index 8e366d3..95f8dbb 100644 --- a/MainPresenter.py +++ b/MainPresenter.py @@ -1,25 +1,26 @@ from network_scan.CoreModel import CoreModel from address_generation.Parser import Parser import threading -import queue import datetime from PyQt5.Qt import * -from netaddr import IPNetwork from address_generation.IpGenerator import IpGenerator - +from storage.JSONStorage import JSONStorage class MainPresenter: def __init__(self, ui): self.ui = ui self.threads = [] self.isScanEnabled = False - self.parser=Parser() + self.parser = Parser() + #needed config to specify path + self.storage = JSONStorage("results.json") def startScan(self, ipRanges, portsStr, threadNumber, timeout): if timeout == '': timeout = '3' addresses = self.parser.parse_address_field(ipRanges) ports = self.parser.parse_port_field(portsStr) + print(ports) self.ip_generator = IpGenerator(addresses,ports) for i in range(int(threadNumber)): self.threads.append(ScanThread(self.ip_generator, ports, timeout, self)) @@ -33,6 +34,7 @@ class MainPresenter: if is_last: self.isScanEnabled = False self.ui.startButton.setText("Start") + self.storage.save() return count = 0 for thr in self.threads: @@ -55,8 +57,6 @@ class MainPresenter: thread.exit_signal.emit(is_last_thread) self.threads.clear() self.ui.currentThreadsLabel.setText("0") - self.queue = queue.Queue() - def setLogText(self, string): self.ui.dataText.append("[" + str(datetime.datetime.now()) + "] " + str(string)) @@ -72,6 +72,7 @@ class ScanThread(QThread): def __init__(self, ip_generator, ports, timeout, presenter, parent=None): QThread.__init__(self, parent) self.ip_generator = ip_generator + self.previous_address = None self.coreModel = CoreModel(timeout) self._stop_event = threading.Event() self.timeout = timeout @@ -79,16 +80,20 @@ class ScanThread(QThread): self.is_running = True def run(self): + print("Thread sterted") while True: - scan_address = self.ip_generator.get_next_address(None) + scan_address = self.ip_generator.get_next_address(self.previous_address) if not scan_address: break + self.previous_address = scan_address scan_result = self.coreModel.scan_address(scan_address) + self.presenter.storage.put_responce(scan_address, scan_result) if scan_result==0: self.signal.emit( '%s has open port: %s' % scan_address) else: self.signal.emit( '%s has closed port: %s' % scan_address) self.is_running = False + print("Thread stopped") count = 0 is_last_thread = False for i in self.presenter.threads: diff --git a/address_generation/IpGenerator.py b/address_generation/IpGenerator.py index ceeed82..08fecfa 100644 --- a/address_generation/IpGenerator.py +++ b/address_generation/IpGenerator.py @@ -1,18 +1,28 @@ from AbstractAddressGenerator import AbstractAddressGenerator from threading import RLock + class IpGenerator(AbstractAddressGenerator): - def __init__(self, address_generator, ports): - self.address_generator = address_generator + def __init__(self, ip_generator, ports): + self.ip_generator = ip_generator self.ports = ports - self.portnum = -1 self.lock = RLock() + def get_next_port_number(self, previous_port): + return (self.ports.index(previous_port) + 1) % len(self.ports) + def get_next_address(self, previous_address): with self.lock: - self.portnum = (self.portnum + 1) % len(self.ports) - try: - return (str(next(self.address_generator)), self.ports[self.portnum]) - except StopIteration: - return None + portnum = 0 + next_ip = None + if previous_address: + next_ip, port = previous_address + portnum = self.get_next_port_number(port) + if (portnum == 0): + try: + next_ip = str(next(self.ip_generator)) + except StopIteration: + return None + result = (next_ip, self.ports[portnum]) + return result diff --git a/address_generation/__init__.py b/address_generation/__init__.py index f882043..d98799d 100644 --- a/address_generation/__init__.py +++ b/address_generation/__init__.py @@ -3,5 +3,4 @@ import os fil = __file__[:__file__.rindex(os.sep)] -print(fil) sys.path.insert(0,fil) diff --git a/address_generation/test_IpGenerator.py b/address_generation/test_IpGenerator.py index 0e6c296..2979b4d 100644 --- a/address_generation/test_IpGenerator.py +++ b/address_generation/test_IpGenerator.py @@ -8,13 +8,19 @@ class testIpGenerator(TestCase): def setUp(self): p = Parser() - self.ipgen = IpGenerator(p.parse_address_field("192.168.1.1"), [80]) + self.ipgen = IpGenerator( + p.parse_address_field("192.168.1.1 - 192.168.1.10"), [80, 90]) def testIpGeneration(self): - self.assertEquals( + '''self.assertEqual( self.ipgen.get_next_address(None), - (IPv4Address("192.168.1.1"), 80)) - print(self.ipgen.get_next_address(None)) + ("192.168.1.1", 80))''' + previous_address = None + a = True + while previous_address or a: + previous_address=self.ipgen.get_next_address(previous_address) + a = False + self.assertEqual(self.ipgen.get_next_address(None), None) if __name__ == "__main__": From 6b8b65d127a43d9eab592f1631f904e40f57de0f Mon Sep 17 00:00:00 2001 From: Zloooy Date: Thu, 31 Jan 2019 21:19:44 +0300 Subject: [PATCH 04/10] Added some docstrings, changed doubled code in MainPresenter. --- MainPresenter.py | 47 +++++++------------ .../AbstractAddressGenerator.py | 8 +++- address_generation/AbstractParser.py | 4 ++ network_scan/AbstractScanner.py | 5 ++ network_scan/__init__.py | 1 - 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/MainPresenter.py b/MainPresenter.py index 95f8dbb..e8f7f57 100644 --- a/MainPresenter.py +++ b/MainPresenter.py @@ -6,6 +6,7 @@ from PyQt5.Qt import * from address_generation.IpGenerator import IpGenerator from storage.JSONStorage import JSONStorage + class MainPresenter: def __init__(self, ui): self.ui = ui @@ -30,33 +31,30 @@ class MainPresenter: thread.exit_signal.connect(self.on_thread_exit) thread.start() - def on_thread_exit(self, is_last): - if is_last: - self.isScanEnabled = False - self.ui.startButton.setText("Start") - self.storage.save() - return + def on_thread_exit(self): count = 0 for thr in self.threads: if thr.is_running: count = count + 1 + if count == len(self.threads): + self.on_end_scanning() self.setCurrentThreadsLabel(count) + def on_end_scanning(self): + self.isScanEnabled = False + self.ui.startButton.setText("Start") + self.storage.save() + def stopScan(self): self.isScanEnabled = False for thread in self.threads: thread.exit() - thread.is_running = False - count = 0 - is_last_thread = False - for i in self.threads: - if not i.is_running: - count += 1 - if count == len(self.threads): - is_last_thread = True - thread.exit_signal.emit(is_last_thread) + for thread in self.threads: + thread.wait() + self.on_end_scanning() self.threads.clear() self.ui.currentThreadsLabel.setText("0") + def setLogText(self, string): self.ui.dataText.append("[" + str(datetime.datetime.now()) + "] " + str(string)) @@ -67,7 +65,7 @@ class MainPresenter: class ScanThread(QThread): signal = pyqtSignal(str) - exit_signal = pyqtSignal(bool) + exit_signal = pyqtSignal() def __init__(self, ip_generator, ports, timeout, presenter, parent=None): QThread.__init__(self, parent) @@ -80,7 +78,6 @@ class ScanThread(QThread): self.is_running = True def run(self): - print("Thread sterted") while True: scan_address = self.ip_generator.get_next_address(self.previous_address) if not scan_address: @@ -88,18 +85,10 @@ class ScanThread(QThread): self.previous_address = scan_address scan_result = self.coreModel.scan_address(scan_address) self.presenter.storage.put_responce(scan_address, scan_result) - if scan_result==0: - self.signal.emit( '%s has open port: %s' % scan_address) + if scan_result == 0: + self.signal.emit('%s has open port: %s' % scan_address) else: - self.signal.emit( '%s has closed port: %s' % scan_address) + self.signal.emit('%s has closed port: %s' % scan_address) self.is_running = False - print("Thread stopped") - count = 0 - is_last_thread = False - for i in self.presenter.threads: - if not i.isRunning(): - count += 1 - if count == len(self.presenter.threads): - is_last_thread = True - self.exit_signal.emit(is_last_thread) + self.exit_signal.emit() self.exit(1) diff --git a/address_generation/AbstractAddressGenerator.py b/address_generation/AbstractAddressGenerator.py index ea1ead1..1e8bfbc 100644 --- a/address_generation/AbstractAddressGenerator.py +++ b/address_generation/AbstractAddressGenerator.py @@ -2,8 +2,14 @@ from abc import abstractmethod, ABC class AbstractAddressGenerator(ABC): - + '''The class describes addess generation mechanism. + In __init__ method it should get results of parsing fields + and then it returns addresses.''' @classmethod @abstractmethod def get_next_address(self, previous_address, **kwargs): + '''Address - an only, indivisible object, that describes single scan + target address. This method should return next address to scan based on + previous scanned address and result of scanning previous address, that + can be placed in kwargs.''' pass diff --git a/address_generation/AbstractParser.py b/address_generation/AbstractParser.py index 36bd85f..e492982 100644 --- a/address_generation/AbstractParser.py +++ b/address_generation/AbstractParser.py @@ -2,13 +2,17 @@ from abc import ABC, abstractmethod class AbstractParser(ABC): + '''The class describes fields parsing mechanisms''' @classmethod @abstractmethod def parse_address_field(self, field): + '''In address field can be plased any text, describing address of + scanning target''' pass @classmethod @abstractmethod def parse_port_field(self, field): + '''In port field only numbers, whitespaces, comma and '-' allowed''' pass diff --git a/network_scan/AbstractScanner.py b/network_scan/AbstractScanner.py index 53d11ae..72716f6 100644 --- a/network_scan/AbstractScanner.py +++ b/network_scan/AbstractScanner.py @@ -2,13 +2,18 @@ from abc import ABC, abstractmethod class AbstractScanner(ABC): + '''The class is used by one thread to scan targets''' @classmethod @abstractmethod def __init__(self, **kwargs): + '''In this method you can init some + reusable resources needed for scan''' pass @classmethod @abstractmethod def scan_address(self, address): + '''This method should contain scanning process of given address. All + items returned will be passed to AbstractStorage and ui''' pass diff --git a/network_scan/__init__.py b/network_scan/__init__.py index f882043..d98799d 100644 --- a/network_scan/__init__.py +++ b/network_scan/__init__.py @@ -3,5 +3,4 @@ import os fil = __file__[:__file__.rindex(os.sep)] -print(fil) sys.path.insert(0,fil) From 3346529f92716ca2878c038e42a110ff1a7d772a Mon Sep 17 00:00:00 2001 From: Zloooy Date: Fri, 1 Feb 2019 18:36:23 +0300 Subject: [PATCH 05/10] Added README.md --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..6797c9b --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +PySca - a NEtwork SCAnner rewritten in Python +============================= +What is it? +------------ +According to [Wikipedia](https://en.wikipedia.org/wiki/Network_enumeration#Software), +network scanner is a computer program used to retrieve usernames and info on groups, shares, and services of networked computers. +This project is based on ideas of scanner [NESCA](https://github.com/pantyusha/nesca) - it is simple scanner for everyone with GUI. The project is not just another fork of nesca - it is analogue built from scratch using Python language. Another difference from original is modularity of code - PySca has ability to get scan results from wherever you want and put wherever you can with the same user interface. + +INSTALLATION +------------ +Just run +```bash +git clone http://github.com/ChronosX88/PySca.git +cd PySca +pip install -r requirments.txt +``` + +RUN +------------ +Run +```bash +python main.py +``` +in root PySca folder From 60061270b68b6e84a951ced26f89cb46fc51b573 Mon Sep 17 00:00:00 2001 From: Zloooy Date: Fri, 1 Feb 2019 20:04:31 +0300 Subject: [PATCH 06/10] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6797c9b..c3b0f01 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ What is it? ------------ According to [Wikipedia](https://en.wikipedia.org/wiki/Network_enumeration#Software), network scanner is a computer program used to retrieve usernames and info on groups, shares, and services of networked computers. -This project is based on ideas of scanner [NESCA](https://github.com/pantyusha/nesca) - it is simple scanner for everyone with GUI. The project is not just another fork of nesca - it is analogue built from scratch using Python language. Another difference from original is modularity of code - PySca has ability to get scan results from wherever you want and put wherever you can with the same user interface. +This project is based on ideas of scanner [NESCA](https://github.com/pantyusha/nesca) - it is simple scanner for everyone. PySca includes GUI-interface. The project is not just another fork of nesca - it is analogue built from scratch using Python language. Another difference from original is modularity of code - PySca has ability to get scan results from wherever you want and put wherever you can with the same user interface. INSTALLATION ------------ @@ -12,7 +12,7 @@ Just run ```bash git clone http://github.com/ChronosX88/PySca.git cd PySca -pip install -r requirments.txt +pip install -r requirements.txt ``` RUN From ad44aa5952d90d89cafc8820d8bfa9cafce9d01c Mon Sep 17 00:00:00 2001 From: Zloooy Date: Sun, 3 Feb 2019 12:13:10 +0300 Subject: [PATCH 07/10] Changed threads to workers in MainPresenter, added russian README --- MainPresenter.py | 105 +++++++++++++++++++++++++++-------------------- README.md | 1 + README.ru.md | 24 +++++++++++ 3 files changed, 85 insertions(+), 45 deletions(-) create mode 100644 README.ru.md diff --git a/MainPresenter.py b/MainPresenter.py index e8f7f57..845cdc1 100644 --- a/MainPresenter.py +++ b/MainPresenter.py @@ -1,8 +1,9 @@ from network_scan.CoreModel import CoreModel from address_generation.Parser import Parser -import threading +from threading import RLock import datetime -from PyQt5.Qt import * +from PyQt5.Qt import QThread, pyqtSignal +from PyQt5.QtCore import QObject, pyqtSlot from address_generation.IpGenerator import IpGenerator from storage.JSONStorage import JSONStorage @@ -15,80 +16,94 @@ class MainPresenter: self.parser = Parser() #needed config to specify path self.storage = JSONStorage("results.json") + self.exit_lock = RLock() def startScan(self, ipRanges, portsStr, threadNumber, timeout): - if timeout == '': - timeout = '3' + timeout = 3 if not timeout else int(timeout) addresses = self.parser.parse_address_field(ipRanges) ports = self.parser.parse_port_field(portsStr) - print(ports) self.ip_generator = IpGenerator(addresses,ports) - for i in range(int(threadNumber)): - self.threads.append(ScanThread(self.ip_generator, ports, timeout, self)) - self.setCurrentThreadsLabel(len(self.threads)) + self.scanner = CoreModel(timeout) + threadNumber = int(threadNumber) + for i in range(threadNumber): + scan_worker = ScanWorker( + self.ip_generator, + self.scanner, + self.storage + ) + scan_thread = QThread() + scan_worker.log_signal.connect(self.log_text) + scan_worker.moveToThread(scan_thread) + scan_worker.exit_signal.connect(scan_thread.exit) + scan_worker.exit_signal.connect(self.on_worker_exit) + scan_thread.started.connect(scan_worker.work) + self.threads.append((scan_worker, scan_thread)) + self.changeThreadLabel(threadNumber) for thread in self.threads: - thread.signal.connect(self.setLogText) - thread.exit_signal.connect(self.on_thread_exit) - thread.start() + scan_worker, scan_thread = thread + scan_thread.start() + + def changeThreadLabel(self,number_of_threads): + self.number_of_threads = number_of_threads + self.ui.currentThreadsLabel.setText(str(number_of_threads)) - def on_thread_exit(self): - count = 0 - for thr in self.threads: - if thr.is_running: - count = count + 1 - if count == len(self.threads): + def on_worker_exit(self): + print("on_worker_exit called") + self.changeThreadLabel(self.number_of_threads - 1) + with self.exit_lock: + for num, thread in enumerate(self.threads): + scan_worker, scan_thread = thread + if not scan_worker.isRunning: + self.threads.pop(num) + break + if self.number_of_threads == 0: self.on_end_scanning() - self.setCurrentThreadsLabel(count) - def on_end_scanning(self): self.isScanEnabled = False self.ui.startButton.setText("Start") self.storage.save() def stopScan(self): - self.isScanEnabled = False - for thread in self.threads: - thread.exit() - for thread in self.threads: - thread.wait() - self.on_end_scanning() - self.threads.clear() - self.ui.currentThreadsLabel.setText("0") + while self.threads: + scan_worker, scan_thread = self.threads[0] + if scan_worker.isRunning: + scan_worker.stop() - def setLogText(self, string): + def log_text(self, string): self.ui.dataText.append("[" + str(datetime.datetime.now()) + "] " + str(string)) def setCurrentThreadsLabel(self, threadNumber): self.ui.currentThreadsLabel.setText(str(threadNumber)) -class ScanThread(QThread): +class ScanWorker(QObject): - signal = pyqtSignal(str) + + log_signal = pyqtSignal(str) exit_signal = pyqtSignal() - def __init__(self, ip_generator, ports, timeout, presenter, parent=None): - QThread.__init__(self, parent) + def __init__(self, ip_generator, scanner, storage, **kwargs): + super().__init__() self.ip_generator = ip_generator + self.storage = storage + self.scanner = scanner self.previous_address = None - self.coreModel = CoreModel(timeout) - self._stop_event = threading.Event() - self.timeout = timeout - self.presenter = presenter - self.is_running = True + self.isRunning = True - def run(self): - while True: + @pyqtSlot() + def work(self): + while self.isRunning: scan_address = self.ip_generator.get_next_address(self.previous_address) if not scan_address: break self.previous_address = scan_address - scan_result = self.coreModel.scan_address(scan_address) - self.presenter.storage.put_responce(scan_address, scan_result) + scan_result = self.scanner.scan_address(scan_address) + self.storage.put_responce(scan_address, scan_result) if scan_result == 0: - self.signal.emit('%s has open port: %s' % scan_address) + self.log_signal.emit('%s has open port: %s' % scan_address) else: - self.signal.emit('%s has closed port: %s' % scan_address) - self.is_running = False + self.log_signal.emit('%s has closed port: %s' % scan_address) + def stop(self): + print("stop called") + self.isRunning = False self.exit_signal.emit() - self.exit(1) diff --git a/README.md b/README.md index c3b0f01..27a37c1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ PySca - a NEtwork SCAnner rewritten in Python ============================= +[Вариант на русском](README.ru.md) What is it? ------------ According to [Wikipedia](https://en.wikipedia.org/wiki/Network_enumeration#Software), diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 0000000..5218213 --- /dev/null +++ b/README.ru.md @@ -0,0 +1,24 @@ +PySca - сетевой сканер, переписанный на Python +============================= +Что это? +------------ +Согласно [Википедии](https://en.wikipedia.org/wiki/Network_enumeration#Software), +сетевой сканер это компьютерная программа, используемая для получения информации о группах, общих ресурсах и сервисах компьютеров, подключённых к Сети. +Этот проект основывается на идеях небезызвестного сканера [NESCA](https://github.com/pantyusha/nesca) - сканер, понятный каждому за счёт [GUI](https://ru.wikipedia.org/wiki/%D0%93%D1%80%D0%B0%D1%84%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8F). PySca - не очередной форк NESCA - это её аналог, созданный с нуля на Python. Другое важное отличие от оригинала - модульность архитектуры сканера - PySca может брать данные (адреса для сканирования) из любого источника и сохранять результат куда возможно (при наличии соответствующих модулей), сохраняя тот же пользовательский интерфейс. + +УСТАНОВКА +------------ +Введите в терминале +```bash +git clone http://github.com/ChronosX88/PySca.git +cd PySca +pip install -r requirements.txt +``` + +ЗАПУСК +------------ +Введите в терминале +```bash +python main.py +``` +Находясь в корневой папке PySca From 535c1a97a30f21ff8a0a0a1aac6b3dd355423f65 Mon Sep 17 00:00:00 2001 From: Zloooy Date: Sun, 3 Feb 2019 13:44:21 +0300 Subject: [PATCH 08/10] Updated README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 27a37c1..8aad164 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ PySca - a NEtwork SCAnner rewritten in Python ============================= [Вариант на русском](README.ru.md) +------------ What is it? ------------ According to [Wikipedia](https://en.wikipedia.org/wiki/Network_enumeration#Software), From f817a2e661d266c9a40308b3e63de33557e0a26e Mon Sep 17 00:00:00 2001 From: Zloooy Date: Sun, 3 Feb 2019 13:46:55 +0300 Subject: [PATCH 09/10] Updated README.ru.md --- README.ru.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.ru.md b/README.ru.md index 5218213..52d337c 100644 --- a/README.ru.md +++ b/README.ru.md @@ -4,7 +4,7 @@ PySca - сетевой сканер, переписанный на Python ------------ Согласно [Википедии](https://en.wikipedia.org/wiki/Network_enumeration#Software), сетевой сканер это компьютерная программа, используемая для получения информации о группах, общих ресурсах и сервисах компьютеров, подключённых к Сети. -Этот проект основывается на идеях небезызвестного сканера [NESCA](https://github.com/pantyusha/nesca) - сканер, понятный каждому за счёт [GUI](https://ru.wikipedia.org/wiki/%D0%93%D1%80%D0%B0%D1%84%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8F). PySca - не очередной форк NESCA - это её аналог, созданный с нуля на Python. Другое важное отличие от оригинала - модульность архитектуры сканера - PySca может брать данные (адреса для сканирования) из любого источника и сохранять результат куда возможно (при наличии соответствующих модулей), сохраняя тот же пользовательский интерфейс. +Этот проект основывается на идеях небезызвестного сканера [NESCA](https://github.com/pantyusha/nesca) - сканера, понятныого каждому за счёт удобного [GUI](https://ru.wikipedia.org/wiki/%D0%93%D1%80%D0%B0%D1%84%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8F). PySca - не очередной форк NESCA - это её аналог, созданный с нуля на Python. Другое важное отличие от оригинала - модульность архитектуры сканера - PySca может брать данные (адреса для сканирования) из любого источника и сохранять результат куда возможно (при наличии соответствующих модулей), сохраняя тот же пользовательский интерфейс. УСТАНОВКА ------------ From b621d51fa022c11cc80a0b3261c8b7323d85681e Mon Sep 17 00:00:00 2001 From: Zloooy Date: Sun, 3 Feb 2019 15:07:59 +0300 Subject: [PATCH 10/10] Removed unused metod --- MainPresenter.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/MainPresenter.py b/MainPresenter.py index 845cdc1..a9143e7 100644 --- a/MainPresenter.py +++ b/MainPresenter.py @@ -22,7 +22,7 @@ class MainPresenter: timeout = 3 if not timeout else int(timeout) addresses = self.parser.parse_address_field(ipRanges) ports = self.parser.parse_port_field(portsStr) - self.ip_generator = IpGenerator(addresses,ports) + self.ip_generator = IpGenerator(addresses, ports) self.scanner = CoreModel(timeout) threadNumber = int(threadNumber) for i in range(threadNumber): @@ -42,22 +42,22 @@ class MainPresenter: for thread in self.threads: scan_worker, scan_thread = thread scan_thread.start() - - def changeThreadLabel(self,number_of_threads): + + def changeThreadLabel(self, number_of_threads): self.number_of_threads = number_of_threads self.ui.currentThreadsLabel.setText(str(number_of_threads)) def on_worker_exit(self): - print("on_worker_exit called") self.changeThreadLabel(self.number_of_threads - 1) - with self.exit_lock: + with self.exit_lock: for num, thread in enumerate(self.threads): - scan_worker, scan_thread = thread - if not scan_worker.isRunning: - self.threads.pop(num) - break + scan_worker, scan_thread = thread + if not scan_worker.isRunning: + self.threads.pop(num) + break if self.number_of_threads == 0: self.on_end_scanning() + def on_end_scanning(self): self.isScanEnabled = False self.ui.startButton.setText("Start") @@ -72,13 +72,9 @@ class MainPresenter: def log_text(self, string): self.ui.dataText.append("[" + str(datetime.datetime.now()) + "] " + str(string)) - def setCurrentThreadsLabel(self, threadNumber): - self.ui.currentThreadsLabel.setText(str(threadNumber)) - class ScanWorker(QObject): - log_signal = pyqtSignal(str) exit_signal = pyqtSignal() @@ -103,7 +99,8 @@ class ScanWorker(QObject): self.log_signal.emit('%s has open port: %s' % scan_address) else: self.log_signal.emit('%s has closed port: %s' % scan_address) + self.stop() + def stop(self): - print("stop called") self.isRunning = False self.exit_signal.emit()