This Network Scanner is a robust and comprehensive tool designed for network administrators and cybersecurity enthusiasts. Leveraging Python's power and third-party libraries such as Scapy and Nmap, this scanner can quickly and efficiently assess devices on a specified IP range, providing valuable insights into the network's structure and potential vulnerabilities.

Key Features:

  1. IP Range Scanning: By specifying a target IP range, the scanner will rapidly detect all active devices, displaying their IP and MAC addresses.
  2. Port Scanning: With an optional flag, users can delve deeper into specific devices by scanning their open ports. This feature assists in identifying potential weak points in the network and services that might be exposed.
  3. OS Detection: This scanner is also equipped to detect the operating system of devices on the network. Employing Nmap's OS detection feature, it provides an educated guess about the OS running on the detected IP, offering a deeper understanding of the network's composition.
  4. Concurrent Scanning: The tool uses a multi-threaded approach, allowing for simultaneous scans of multiple ports, ensuring the scanning process is fast and efficient.
  5. User-Friendly Output: Utilizing color-coding, the scanner ensures that results are easily discernible, with open ports highlighted in green and closed ports in gray.
  6. Logging: For thorough analysis, all discovery details and potential errors are logged into a file, enabling users to review and assess the scan's results later.
  7. Flexibility: Users can customize their scans using command-line arguments, deciding the range of ports to scan and whether to conduct an OS detection or not.
import argparse
import logging
import socket 
from scapy.all import ARP, Ether, srp
import nmap 
from threading import Thread, Lock
from queue import Queue
from colorama import init, Fore

# some colors
init()GREEN = Fore.GREEN
GRAY = Fore.LIGHTBLACK_EX
RESET = Fore.RESET

# number of threads and queue setup
N_THREADS = 50
q = Queue()
print_lock = Lock()
terminate_flag = False # for graceful thread termination

def parse_arguments():
    parser = argparse.ArgumentParser(description="Network and Port Scanner")
    parser.add_argument("-t", "--target", help="Target IP range", required=True)
    parser.add_argument("-p", "--portscan", help="Perform port scan", action="store_true")
    parser.add_argument("-r", "--portrange", default="1-65535", help="Range of ports to scan (e.g., 1-65535). Default is all ports.")
    parser.add_argument("-o", "--osdetect", help="Perform OS detection", action="store_true")
    return parser.parse_args()

def port_scan(host, port, open_ports, print_lock):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)  # add a timeout
    retries = 3  # Number of retries
    connected = False

    for _ in range(retries):
        try:
            s.connect((host, port))
            connected = True
            break
        except:
            pass

    if connected:
        with print_lock:
            print(f"{GREEN}{host:15}:{port:5} is open  {RESET}")
        open_ports.append(port)
    else:
        with print_lock:
            print(f"{GRAY}{host:15}:{port:5} is closed{RESET}", end='\r')
        s.close()

def scan_thread(host, open_ports, print_lock):
    global terminate_flag
    while not terminate_flag:
        worker = q.get()
        if worker is None:
            break
        port_scan(host, worker, open_ports, print_lock)
        q.task_done()

def detect_os(ip):
    nm = nmap.PortScanner()
    try:
        nm.scan(ip, arguments='-O')
        return nm[ip]['osclass']['osfamily']
    except Exception as e:
        logging.error(f"Error detecting OS for {ip}: {str(e)}")
        return None

def main():
    args = parse_arguments()
    logging.basicConfig(filename='network_scan.log', level=logging.INFO, format='%(asctime)s - %(message)s')
    arp = ARP(pdst=args.target)
    ether = Ether(dst="ff:ff:ff:ff:ff:ff")
    packet = ether/arp    result = srp(packet, timeout=3, verbose=0)[0]

    clients = [{'ip': received.psrc, 'mac': received.hwsrc} for sent, received in result]

    start_port, end_port = map(int, args.portrange.split('-'))
    ports_to_scan = range(start_port, end_port + 1)

    for client in clients:
        host = client['ip']
        open_ports = []

        logging.info(f"Discovered: {host} at {client['mac']}")
        print(f"Discovered: {host} at {client['mac']}")

        if args.portscan:
            threads = []
            for _ in range(N_THREADS):
                t = Thread(target=scan_thread, args=(host, open_ports, print_lock))
                t.start()
                threads.append(t)

            for worker in ports_to_scan:
                q.put(worker)
            for _ in range(N_THREADS):
                q.put(None)
            # Setting the flag for threads to exit
            global terminate_flag
            terminate_flag = True

            for t in threads:
                t.join()

        if args.osdetect:
            os_guess = detect_os(host)
            if os_guess:
                logging.info(f"OS guess for {host}: {os_guess}")
                print(f"OS guess for {host}: {os_guess}")

if __name__ == "__main__":
    main()