Ora

How to Create a Network Scanner in Python?

Published in Network Scanning 5 mins read

Creating a network scanner in Python involves building a script that can identify active hosts, discover open ports, and sometimes even detect services running on a target network or specific IP addresses. This powerful tool is fundamental for network administrators and security professionals to understand network topology and potential vulnerabilities.

What is a Python Network Scanner?

A Python network scanner is a program that uses network protocols to probe hosts and ports, reporting on their status. This can range from a simple script that checks if a host is alive to a sophisticated tool that maps out an entire network's services and operating systems.

Essential Tools and Libraries for Network Scanning

Python offers several built-in modules and third-party libraries that are crucial for developing network scanning capabilities:

  • socket: The core Python module for low-level network communication. It allows you to create sockets, connect to hosts, and send/receive data, making it ideal for port scanning.
  • subprocess: Used to run external system commands, such as ping or nmap, from within your Python script.
  • argparse: An excellent module for parsing command-line arguments, allowing users to specify target IPs, port ranges, and other options when running your scanner.
  • Scapy: A powerful third-party library for packet manipulation. It allows you to forge or decode packets of a large number of protocols, send them on the wire, capture them, match requests and replies, and much more. It's incredibly versatile for advanced scanning techniques like ARP scans or crafting custom packets.
    • Installation: pip install scapy
  • datetime / time: Useful for timing the scanning process and for logging.

Step-by-Step Guide to Building a Basic Network Scanner

Developing a network scanner typically follows a structured approach, allowing for modularity and scalability. Here's how to build one in Python:

Step 1: Importing Necessary Modules

The first step is to import all the required modules. This sets up your script with the functionalities it will need.

import socket
import subprocess
import argparse
import sys
from datetime import datetime
import threading # For concurrent scanning

Step 2: Handling Command-Line Arguments

To make your scanner user-friendly and flexible, you'll want to allow users to pass arguments via the command line, such as the target IP address or hostname and the range of ports to scan. The argparse module is perfect for this.

def get_arguments():
    parser = argparse.ArgumentParser(description="A simple Python network scanner.")
    parser.add_argument("-t", "--target", dest="target", help="Target IP address or hostname (e.g., 192.168.1.1 or example.com)")
    parser.add_argument("-p", "--ports", dest="ports", help="Port range to scan (e.g., 1-1024 or 80,443,8080)")
    options = parser.parse_args()
    if not options.target:
        parser.error("[-] Please specify a target, use --help for more info.")
    return options

# Example usage:
# options = get_arguments()
# target = options.target
# port_range_str = options.ports

This code snippet defines arguments for the target and the ports. You'd then parse the port_range_str to create a list of integers for scanning.

Step 3: Implementing Network Scanning Functionality

This is the core of your scanner. It involves writing functions to perform host discovery (e.g., ping) and port scanning.

  • Host Discovery (Ping Scan): Check if a host is active.

    def is_host_up(ip_address):
        try:
            # -c 1 for 1 packet, -w 1 for 1 second timeout (Linux/macOS)
            # -n 1 for 1 packet, -w 1000 for 1000ms timeout (Windows)
            param = "-n" if sys.platform.startswith("win") else "-c"
            command = ["ping", param, "1", ip_address]
            response = subprocess.call(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            return response == 0
        except Exception:
            return False
  • Port Scanning: Attempt to connect to specific ports to determine if they are open.

    def scan_port(ip_address, port):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(1) # 1 second timeout
            result = sock.connect_ex((ip_address, port)) # Returns 0 if successful, otherwise an error code
            if result == 0:
                try:
                    service = socket.getservbyport(port)
                except OSError:
                    service = "Unknown"
                return True, service
            else:
                return False, None
        except socket.error as e:
            return False, None
        finally:
            sock.close()
    
    def perform_port_scan(target_ip, ports_to_scan):
        print(f"\n[*] Scanning ports on {target_ip}...")
        open_ports = []
        for port in ports_to_scan:
            status, service = scan_port(target_ip, port)
            if status:
                open_ports.append({'port': port, 'service': service})
        return open_ports

Step 4: Formatting and Printing Results

After scanning, you need to present the information clearly. This involves printing which hosts are up and which ports are open, along with any identified services.

def print_results(target, start_time, end_time, open_ports, host_is_up):
    print("-" * 50)
    print(f"Scanning Target: {target}")
    print(f"Scan Started at: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
    print("-" * 50)

    if not host_is_up:
        print(f"Host {target} appears to be down or unreachable.")
        print(f"Scan Finished at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
        print(f"Scan Duration: {end_time - start_time}")
        return

    if open_ports:
        print("Open Ports:")
        # Display results in a table for clarity
        print(f"{'Port':<8} {'Service':<15}")
        print(f"{'----':<8} {'-------':<15}")
        for p in open_ports:
            print(f"{p['port']:<8} {p['service']:<15}")
    else:
        print("No open ports found within the specified range.")

    print("-" * 50)
    print(f"Scan Finished at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"Scan Duration: {end_time - start_time}")
    print("-" * 50)

Putting It All Together (Main Script Logic):

def main():
    options = get_arguments()
    target = options.target
    port_range_str = options.ports if options.ports else "1-1024" # Default port range

    # Resolve hostname to IP address
    try:
        target_ip = socket.gethostbyname(target)
    except socket.gaierror:
        print(f"[-] Hostname could not be resolved: {target}")
        sys.exit()

    ports_to_scan = []
    if '-' in port_range_str:
        start_port, end_port = map(int, port_range_str.split('-'))
        ports_to_scan = range(start_port, end_port + 1)
    else:
        ports_to_scan = [int(p) for p in port_range_str.split(',')]

    print(f"[*] Starting scan on {target_ip} ({target})...")
    start_time = datetime.now()

    host_up = is_host_up(target_ip)
    open_ports = []

    if host_up:
        open_ports = perform_port_scan(target_ip, ports_to_scan)

    end_time = datetime.now()
    print_results(target, start_time, end_time, open_ports, host_up)

if __name__ == "__main__":
    main()

Advanced Scanning with Scapy

For more sophisticated scanning, like ARP poisoning, crafting custom packets, or stealth scanning (SYN scans), Scapy is invaluable.

from scapy.all import *

def arp_scan(ip_range):
    """
    Performs an ARP scan to discover active hosts on a local network.
    """
    print(f"\n[*] Performing ARP scan on {ip_range}...")
    try:
        ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip_range), timeout=2, verbose=0)
        active_hosts = []
        for s, r in ans:
            active_hosts.append({'IP': r.psrc, 'MAC': r.hwsrc})
        return active_hosts
    except Exception as e:
        print(f"[-] Error during ARP scan: {e}")
        return []

# Example usage:
# active_hosts = arp_scan("192.168.1.1/24")
# if active_hosts:
#     print("\nActive Hosts (ARP Scan):")
#     for host in active_hosts:
#         print(f"  IP: {host['IP']}, MAC: {host['MAC']}")

Best Practices and Considerations

  • Permissions: Always ensure you have explicit permission before scanning any network you do not own or manage. Unauthorized scanning can be illegal and lead to severe consequences.
  • Error Handling: Implement robust try-except blocks to gracefully handle network issues, timeouts, and invalid inputs.
  • Performance: For scanning large port ranges or multiple hosts, consider using threading or asyncio to perform scans concurrently, significantly speeding up the process.
  • Stealth vs. Speed: Basic socket.connect_ex scans are "full connect" scans, which are easily detectable. More advanced scanners use SYN (half-open) scans, often requiring raw socket access (and thus root privileges on Linux) or libraries like Scapy, to be less conspicuous.
  • Resource Management: Always close sockets (sock.close()) after use to prevent resource leaks.

Comparing Basic Socket Scanning vs. Scapy

Feature Basic Socket Scanner (e.g., socket module) Scapy
Ease of Use Simpler for basic TCP/UDP connect scans. Steeper learning curve, but powerful.
Protocols Primarily TCP/UDP connections. Supports virtually all protocols (IP, ARP, ICMP, DNS, HTTP, etc.).
Packet Crafting Limited to standard connections. Full control over packet fields.
Scan Types Full-connect TCP, basic UDP. SYN scans, ARP scans, custom packet attacks, many more.
Requirements Standard user privileges (for connect scans). Often requires root/admin privileges for raw socket access.
Performance Can be slow without concurrency. Fast, especially for raw packet manipulation.
Use Case Simple port scanning, network status checks. Network security research, penetration testing, network troubleshooting, advanced attacks.

By following these steps and understanding the underlying principles, you can effectively build and utilize network scanners in Python for various purposes, from network monitoring to security auditing.