From ee2de0decf8a4df017d7ebe1c326ceb585c999da Mon Sep 17 00:00:00 2001 From: Zloooy Date: Sun, 27 Jan 2019 23:28:58 +0300 Subject: [PATCH] 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)