DEV Community

Cover image for Network Scanning with Python: ARP, Port, and DNS Scanner
CJ
CJ

Posted on

Network Scanning with Python: ARP, Port, and DNS Scanner

Introduction

Network security and reconnaissance are essential skills for cybersecurity professionals. In this blog post, we will build a Python-based network scanner that performs ARP scanning, port scanning, and DNS resolution using the scapy, socket, dns.resolver, and threading libraries. We will also use rich for better console output.

Overview of the Scanner

  • Perform an ARP scan to discover active hosts on the network.
  • Scan common ports on discovered hosts.
  • Resolve DNS records for a given domain.

Flowchart of the Scanner:

Scanner Flowchart

Script Breakdown

Parsing Command-Line Arguments

The script accepts command-line arguments using argparse to select between port scanning and DNS scanning.

ARP Scanning

The Address Resolution Protocol (ARP) scan sends requests to devices in the network and collects responses to determine active hosts.

ARP Scanning

Port Scanning

For each discovered active host, the script attempts to connect to common ports (e.g., 22, 80, 443) to check if they are open.

Port Scanning

DNS Resolution

If DNS mode is enabled, the script retrieves the A records (IPv4 addresses) of a given domain.

Code

import argparse  # Importing argparse for handling command-line arguments
import socket  # Importing socket for network communication
import scapy.all as scapy  # Importing scapy for ARP scanning
import dns.resolver  # Importing dns.resolver for DNS scanning
import threading  # Importing threading for concurrent port scanning
from rich.console import Console  # Importing rich.console for formatted console output
from rich.table import Table  # Importing rich.table for displaying results in table format
from rich.text import Text  # Importing rich.text for text formatting
from rich import print  # Importing print from rich to enhance terminal output

console = Console()  # Initializing a rich console instance

def parser():
    global target, port_enabled, dns_enabled  # Defining global variables to store user inputs

    arg_parser = argparse.ArgumentParser(description="Options for scanning")  # Creating an argument parser
    arg_parser.add_argument('-t', "--target", help="Specify target explicitly")  # Adding argument for target IP/domain

    req_args = arg_parser.add_mutually_exclusive_group(required=True)  # Ensuring only one option (port or DNS) is selected
    req_args.add_argument("--port", action="store_true", help="Enable port scanning")  # Port scanning option
    req_args.add_argument("--dns", action="store_true", help="Enable DNS scanning")  # DNS scanning option

    args = arg_parser.parse_args()  # Parsing command-line arguments
    target = args.target  # Storing target input
    port_enabled = args.port  # Storing port scanning option
    dns_enabled = args.dns  # Storing DNS scanning option

def arp_scan():
    global active_hosts  # Defining a global list to store active hosts
    active_hosts = []  # Initializing an empty list

    # Creating an ARP request to discover active devices in the network
    arp_request = scapy.ARP(pdst=target if target else "192.168.1.1/24")  
    broadcast = scapy.Ether(dst='ff:ff:ff:ff:ff:ff')  # Creating an Ethernet frame for broadcast
    req_broadcast = broadcast / arp_request  # Combining Ethernet frame with ARP request

    clients = scapy.srp(req_broadcast, timeout=1, verbose=False)[0]  # Sending request and capturing responses

    # Creating a table for displaying active hosts
    table = Table(title="Active Hosts", show_header=True, header_style="bold cyan")
    table.add_column("IP Address", style="green")
    table.add_column("MAC Address", style="magenta")
    table.add_column("Device Name", style="yellow")

    for sent, received in clients:  # Iterating through received ARP responses
        ip = received.psrc  # Extracting IP address
        hostname = get_hostname(ip)  # Fetching device name
        active_hosts.append(ip)  # Adding IP to active hosts list
        table.add_row(ip, received.hwsrc, hostname)  # Adding row to table

    console.print(table)  # Displaying the table

def get_hostname(ip):
    try:
        hostname = socket.gethostbyaddr(ip)[0]  # Resolving hostname from IP
        return hostname  # Returning the hostname
    except socket.herror:
        return "Unknown"  # Returning 'Unknown' if resolution fails

def scan_port(host, port):
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:  # Creating a socket object
            s.settimeout(1)  # Setting a timeout for connection attempts
            result = s.connect_ex((host, port))  # Checking if port is open

            if result == 0:
                console.print(f"[bold green]Open port {port} on {host}[/bold green]")  # Printing open ports
    except Exception as e:
        console.print(f"[bold red]Error scanning {host}:{port} - {e}[/bold red]")  # Handling errors

def multi_thread_scan_ports(host, ports):
    threads = []  # Creating a list to store threads
    for port in ports:  # Iterating over ports to scan
        thread = threading.Thread(target=scan_port, args=(host, port))  # Creating a new thread
        thread.start()  # Starting the thread
        threads.append(thread)  # Adding thread to the list

    for thread in threads:
        thread.join()  # Waiting for all threads to complete

def dns_scan():
    try:
        result = dns.resolver.resolve(target, 'A')  # Resolving A records of the target domain

        table = Table(title="DNS MX Records", show_header=True, header_style="bold cyan")  # Creating a table for results
        table.add_column("Mail Exchange (MX) Records", style="yellow")

        for val in result:
            table.add_row(str(val))  # Adding resolved DNS records to the table

        console.print(table)  # Printing the table
    except Exception as e:
        console.print(f"[bold red]DNS scan failed: {e}[/bold red]")  # Handling errors

if __name__ == "__main__":
    ports_to_scan = [21, 22, 23, 25, 53, 80, 443, 8080]  # Defining common ports to scan

    parser()  # Parsing command-line arguments

    if port_enabled:
        console.print("[bold cyan]Starting ARP scan...[/bold cyan]")  # Indicating ARP scan start
        arp_scan()  # Running ARP scan
        for host in active_hosts:
            console.print(f"[bold yellow]Scanning ports on {host}...[/bold yellow]")  # Indicating port scan start
            multi_thread_scan_ports(host, ports_to_scan)  # Scanning ports using multithreading
    else:
        console.print("[bold cyan]Starting DNS scan...[/bold cyan]")  # Indicating DNS scan start
        dns_scan()  # Running DNS scan
Enter fullscreen mode Exit fullscreen mode

Running the Scanner

To run the scanner, use one of the following commands:

  • Port scanning:
python scanner.py -t 192.168.1.1 --port
Enter fullscreen mode Exit fullscreen mode
  • DNS scanning:
python scanner.py -t example.com --dns
Enter fullscreen mode Exit fullscreen mode

Hey, I'm CJ, a curious mind who loves breaking down tech stuff into simple, fun stories. If you enjoyed this read, follow me for more posts like this, and check out my other socials to see what I'm building next!
X (Formerly Twitter) | GitHub | Linkedin

Top comments (0)