Build a Network Reconnaissance Scanner
Craft raw ARP and TCP packets in Python to map devices and open ports.
Introduction
30 Second Summary
Every device on your home network is constantly chatting with its neighbours. Your router, your phone, your smart TV, and your laptop all announce themselves to each other using a protocol called ARP. Security professionals tap into these conversations to map out what's connected to a network before testing it for vulnerabilities.
In this project, you will build a network reconnaissance scanner using Python and Scapy. Your scanner will discover every device on your local network and probe them for open ports, just like professional penetration testers do at the start of every engagement.
What You'll Build
A command-line reconnaissance tool called recon_scanner.py that crafts raw network packets to discover devices and detect open services on your local network.
By the end of this project, you'll have:
- A working ARP scanner that discovers all live devices on your network, showing their IP and MAC addresses.
- A TCP SYN port scanner that detects open services on discovered hosts without completing a full handshake.
- A polished command-line tool with argument parsing, error handling, and formatted table output.
- Secret Mission: Add an ICMP ping sweep mode and export scan results to a CSV file.
Are there any prerequisites?
You should be comfortable with basic Python concepts like variables, functions, loops, and lists. You also need a Windows computer for this project, since the scanner uses Npcap for raw packet access.
Before We Start
Before you can craft network packets, you need a few tools installed on your machine.
In this step, you will confirm your Python version, install a packet capture driver, and install the Scapy library that lets Python send and receive raw network packets.
In this step, get ready to:
- Verify Python 3.7 or higher is installed and on PATH.
- Install Npcap, the Windows packet capture driver.
- Install Scapy 2.7.0 and confirm it loads correctly.
Verify your Python installation
Scapy requires Python 3.7 or higher. You need to confirm your version before installing any packages.
- Press the Windows key to open your search bar.
- Type PyCharm and press Enter to open it.
- Open the terminal inside PyCharm by clicking Terminal in the bottom toolbar.
- Check your Python version by running this command:
python --version
✔️ I see version 3.7 or higher
Your Python version is ready. Move on to the next substep.
ⓧ I see an older version
You need Python 3.7 or higher for Scapy to work.
- Open your browser and go to https://www.python.org/downloads/.
- Download the latest Python installer for Windows.
- Run the installer. On the first screen, check Add Python to PATH before clicking Install Now.
- After installation completes, close and reopen PyCharm.
- Open the terminal again and run python --version to confirm you see 3.7 or higher.
ⓧ Command not found
Python is not installed or not on your PATH.
- Open your browser and go to https://www.python.org/downloads/.
- Download the latest Python installer for Windows.
- Run the installer. On the first screen, check Add Python to PATH before clicking Install Now.
- After installation completes, close and reopen PyCharm.
- Open the terminal again and run python --version to confirm you see a version number.
Install Npcap
Scapy needs a packet capture driver to send and receive raw network traffic on Windows. Npcap is the modern driver that provides this access.
- Open your browser and go to https://npcap.com.
- Download the Npcap 1.88 installer.
- Run the installer.
- When you see the options checkboxes, uncheck the box labeled Install Npcap in WinPcap API-compatible Mode. This box is checked by default, so you need to actively uncheck it.
- Leave all other options at their defaults.
- Click Install and wait for the installation to complete.
- Click Finish to close the installer.
Why uncheck WinPcap API-compatible Mode?
The Scapy documentation recommends turning off this mode. WinPcap API-compatible mode replaces the old WinPcap driver files in your system directory, which can cause conflicts. Leaving it unchecked installs Npcap cleanly in its own directory.
Installer not running?
If Windows blocks the installer with a SmartScreen warning, click More info and then Run anyway. Npcap is a trusted driver signed by the Nmap Project.
Help me troubleshoot the Npcap installation on Windows.
Install Scapy
With Npcap providing the low-level packet access, you can now install Scapy. Scapy is the Python library that lets you craft, send, and receive network packets directly.
- Install Scapy by running this command in PyCharm's terminal:
pip install scapy==2.7.0
What does this command do?
pip is Python's package manager. The ==2.7.0 part pins the exact version so your scanner works consistently with this tutorial.
- Verify the installation by running this command:
python -c "from scapy.all import ARP; print('Scapy ready')"
You should see Scapy ready printed with no errors.
Seeing an import error?
If you see an error mentioning Npcap or WinPcap, Npcap may not have installed correctly. Rerun the Npcap installer and make sure the installation completes without errors.
If you see ModuleNotFoundError: No module named 'scapy', your pip may be pointing to a different Python installation. Run pip --version and confirm it shows the same Python version as python --version.
Help me fix my Scapy installation error on Windows.
Your environment is ready. Next up, you will build the ARP scanner that discovers every device on your local network.
Set Up Your Environment
Your tools are installed and verified. Now it's time to prepare your working environment for building the network scanner.
Network scanning requires crafting raw packets, which Windows restricts to processes running with elevated privileges. You'll reopen PyCharm as Administrator and create the Python file that will become your reconnaissance tool.
In this step, get ready to:
- Reopen PyCharm with Administrator privileges for raw packet access.
- Create recon_scanner.py and configure Scapy imports.
Reopen PyCharm as Administrator
Scapy sends raw network packets at the Ethernet and IP layers. On Windows, this requires Administrator privileges. Without them, your scanner will throw a PermissionError the moment it tries to send its first packet.
- Close your current PyCharm window.
- Press the Windows key to open your search bar.
- Type PyCharm in the search bar.
- Right-click on the PyCharm result and select Run as administrator.
- Click Yes on the User Account Control prompt that appears.
PyCharm opens and displays "(Administrator)" in its title bar. This confirms it has the elevated privileges your scanner needs.
Don't see (Administrator) in the title bar?
Some versions of PyCharm may not show the indicator in the title bar. You can verify by opening Task Manager, switching to the Details tab, right-clicking a column header, selecting Select columns, and enabling the Elevated column. Find the PyCharm process and confirm it shows "Yes" in the Elevated column.
Help me verify PyCharm is running as Administrator
Why does Scapy need Administrator privileges?
Normal programs use high-level network APIs that the operating system controls. Scapy bypasses these entirely and constructs packets from scratch at the Ethernet frame level. Windows restricts this kind of raw socket access to Administrator processes because a malicious program with raw packet access could impersonate other devices on the network.
Create the scanner file and write imports
With PyCharm running as Administrator, you'll create the Python file that becomes your reconnaissance scanner and import the libraries it needs.
- In PyCharm, open the project or directory you want to work in (or use the default project).
- Right-click on the project folder in the Project panel on the left.
- Select New, then Python File.
- Type recon_scanner and press Enter. PyCharm creates recon_scanner.py and opens it in the editor.
- Add the following imports and configuration to recon_scanner.py by pasting this code:
from scapy.all import ARP, Ether, srp, IP, TCP, sr, conf
import argparse
import ipaddress
conf.verb = 0
What does this code do?
- ARP and Ether are for crafting Layer 2 (Ethernet) packets. srp() sends and receives packets at this layer.
- IP and TCP are for crafting Layer 3/4 packets. sr() sends and receives packets at the IP layer.
- argparse handles command-line arguments so your tool can accept target IPs and port lists.
- ipaddress is a standard library module for sorting and validating IP addresses.
- conf.verb = 0 suppresses Scapy's built-in verbose output so your scanner's own formatted output stays clean.
- Save recon_scanner.py by pressing Ctrl+S.
- Verify the file has no errors by checking that PyCharm does not show any red underlines beneath the import lines.
Seeing red underlines under the Scapy import?
Make sure PyCharm is using the same Python interpreter where you installed Scapy. Go to File > Settings > Project: [your project] > Python Interpreter and confirm it points to the Python installation where you ran pip install scapy==2.7.0.
Help me fix my Scapy import error in PyCharm
Why Scapy instead of Python's socket library?
Python's built-in socket library relies on the operating system's TCP stack to complete full connections. This is slower and generates more network noise. Scapy lets you build packets field by field, send them raw, and inspect responses at every protocol layer.
This is the same approach professional penetration testers use. Tools like Nmap work this way under the hood. Building your own scanner teaches you what those professional tools are actually doing.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
from scapy.all import ARP, Ether, srp, IP, TCP, sr, conf
import argparse
import ipaddress
conf.verb = 0
Your environment is ready and your scanner file is set up with all the imports it needs. Next up, you'll write the ARP scan function that discovers every device on your local network.
Build the ARP Network Discovery Scanner
Your environment is locked and loaded. Now it's time to write the core of your reconnaissance tool: the ARP scanner.
Every device on a local network must respond to ARP requests. By broadcasting an ARP "Who has this IP?" message to every address in a subnet, you can map out which devices are alive and grab their MAC addresses. This is the exact first step a penetration tester takes when scoping a network.
In this step, get ready to:
- Write an ARP scan function that discovers live devices on your subnet.
- Write a display function that formats results into a readable table.
- Run the scanner and confirm it detects devices on your network.
Write the ARP scan function
The arp_scan() function crafts an ARP request, broadcasts it to every IP in a target range, and collects the responses into a list of discovered hosts.
- In recon_scanner.py, add the following function below the conf.verb = 0 line:
def arp_scan(target):
"""Discover live hosts on a local subnet using ARP."""
print(f"\n[*] Starting ARP scan on {target}...")
# Create an ARP request targeting the given IP range
arp_request = ARP(pdst=target)
# Create an Ethernet frame addressed to the broadcast MAC
broadcast = Ether(dst="ff:ff:ff:ff:ff:ff")
# Combine the Ethernet frame and ARP request into one packet
packet = broadcast / arp_request
# Send the packet at Layer 2 and capture responses
answered, _ = srp(packet, timeout=3, verbose=False)
# Extract IP and MAC from each response
hosts = []
for sent, received in answered:
hosts.append({
"ip": received[ARP].psrc,
"mac": received[ARP].hwsrc
})
# Sort hosts by IP address for clean output
hosts.sort(key=lambda h: ipaddress.IPv4Address(h["ip"]))
return hosts
What does this code do?
ARP(pdst=target) creates an ARP "Who has this IP?" request for every address in the target range (like 192.168.1.0/24).
Ether(dst="ff:ff:ff:ff:ff:ff") wraps it in an Ethernet frame addressed to the broadcast MAC address. This ensures every device on the network receives the packet.
srp() sends the combined packet at Layer 2 and waits up to 3 seconds for replies. Each device that responds reveals its IP and MAC address, which we store in a list of dictionaries.
Why srp() instead of sr()?
Scapy provides two sending functions. srp() operates at Layer 2 (Ethernet frames), which is required for ARP because ARP itself is a Layer 2 protocol. sr() operates at Layer 3 (IP packets) and is used for TCP and ICMP scanning, which you will add in later steps.
- Save recon_scanner.py.
You won't see any output yet. The next chunk adds a display function to format the results.
Seeing a red underline on an import?
If PyCharm shows a warning under ARP, Ether, or srp, confirm that scapy installed correctly in the previous step. Re-run pip install scapy==2.7.0 in the terminal if needed.
help me fix Scapy import errors in PyCharm
Write the display results function
Now you need a way to present the scan results. The display_results() function prints discovered hosts in a formatted table with aligned columns.
- Below the arp_scan() function in recon_scanner.py, add this function:
def display_results(hosts, scan_ports=None):
"""Display scan results in a formatted table."""
print("\n" + "=" * 60)
print(f"{'IP Address':<20}{'MAC Address':<25}{'Open Ports'}")
print("=" * 60)
for host in hosts:
# Determine port display string
ports_str = ""
if scan_ports and host.get("open_ports"):
ports_str = ", ".join(str(p) for p in host["open_ports"])
elif scan_ports:
ports_str = "None found"
print(f"{host['ip']:<20}{host['mac']:<25}{ports_str}")
print("=" * 60)
print(f"\n[+] Scan complete. {len(hosts)} device(s) found.")
What does this code do?
The function prints a table header with three columns: IP Address, MAC Address, and Open Ports. The :<20 and :<25 syntax left-aligns each value within a fixed-width column so the output lines up neatly.
The scan_ports parameter is included now so the same function can display port scan results in the next step. For this step, it defaults to None and the Open Ports column stays empty.
- Save recon_scanner.py.
You won't see output yet. The next substep adds a temporary test block so you can run the scanner.
Syntax error on the f-string?
Make sure you copied the curly braces and angle brackets exactly. The pattern {host['ip']:<20} uses single quotes inside the f-string because the f-string itself uses double quotes.
help me fix f-string syntax errors in my display_results function
Run the scanner on your local network
To test the scanner, you need to know your local subnet. You also need to add a temporary test block at the bottom of the file so the functions actually execute when you run the script.
- Find your local subnet by running this command in the PyCharm terminal:
ipconfig
How do I read the output?
Look for the section labelled "Ethernet adapter" or "Wireless LAN adapter" (whichever you use for internet). Find the line that says IPv4 Address. If your IP is 192.168.1.5, your subnet is 192.168.1.0/24. Replace the last number with 0 and add /24 at the end.
Common home subnets are 192.168.1.0/24, 192.168.0.0/24, or 10.0.0.0/24.
- Add a temporary test block at the very bottom of recon_scanner.py so you can run the scanner:
# Temporary test block - will be replaced with argparse in a later step
if __name__ == "__main__":
hosts = arp_scan([[SUBNET="your subnet e.g. 192.168.1.0/24"]])
display_results(hosts)
What is this test block?
The if __name__ == "__main__": block runs only when you execute the file directly. It calls arp_scan() with your subnet, then passes the results to display_results(). You will replace this temporary block with a proper command-line interface in Step 4.
- Run the script by clicking the green play button in PyCharm, or by running this in the terminal:
python recon_scanner.py
What should I see?
You should see output like this:
[*] Starting ARP scan on 192.168.1.0/24...
============================================================
IP Address MAC Address Open Ports
============================================================
192.168.1.1 aa:bb:cc:dd:ee:ff
192.168.1.5 11:22:33:44:55:66
============================================================
[+] Scan complete. 2 device(s) found.
Your IPs and MACs will be different. You should see at least one device (your router, usually the lowest IP like .1). If you see multiple devices, those are other computers, phones, or smart home devices on your network.
- After confirming the output, remove the temporary test block (the last 4 lines starting with the comment # Temporary test block) from recon_scanner.py.
- Save the file.
No devices found or permission error?
- If you see a PermissionError or a message about raw sockets, confirm that PyCharm is running as Administrator. Close PyCharm, right-click its icon, and select Run as administrator.
- If you see a RuntimeError mentioning Npcap, make sure Npcap is installed. Re-download from https://npcap.com if needed.
- If the scan returns zero devices, double-check your subnet value. Run ipconfig again and verify the first three octets of your IPv4 address match the subnet you entered.
help me troubleshoot why my ARP scan finds no devices
Only scan networks you own
Network scanning should only be performed on networks you own or have explicit permission to scan. Your home network is fine for this project. Scanning networks without authorization can be illegal.
✔️ Awesome, I've got everything!
Great. Double check you have saved recon_scanner.py and removed the temporary test block.
ⓧ I'd like to double check the full code
Here is what recon_scanner.py should look like at the end of this step (without the temporary test block):
from scapy.all import ARP, Ether, srp, IP, TCP, sr, conf
import argparse
import ipaddress
conf.verb = 0
def arp_scan(target):
"""Discover live hosts on a local subnet using ARP."""
print(f"\n[*] Starting ARP scan on {target}...")
arp_request = ARP(pdst=target)
broadcast = Ether(dst="ff:ff:ff:ff:ff:ff")
packet = broadcast / arp_request
answered, _ = srp(packet, timeout=3, verbose=False)
hosts = []
for sent, received in answered:
hosts.append({
"ip": received[ARP].psrc,
"mac": received[ARP].hwsrc
})
hosts.sort(key=lambda h: ipaddress.IPv4Address(h["ip"]))
return hosts
def display_results(hosts, scan_ports=None):
"""Display scan results in a formatted table."""
print("\n" + "=" * 60)
print(f"{'IP Address':<20}{'MAC Address':<25}{'Open Ports'}")
print("=" * 60)
for host in hosts:
ports_str = ""
if scan_ports and host.get("open_ports"):
ports_str = ", ".join(str(p) for p in host["open_ports"])
elif scan_ports:
ports_str = "None found"
print(f"{host['ip']:<20}{host['mac']:<25}{ports_str}")
print("=" * 60)
print(f"\n[+] Scan complete. {len(hosts)} device(s) found.")
Your ARP scanner is discovering devices on your network. Next up, you will add TCP port scanning to detect what services are running on each discovered host.
Add TCP Port Scanning
In the last step, you built an ARP scanner that discovers live devices on your local network. Knowing which devices exist is useful, but it doesn't tell you what those devices are actually doing.
TCP port scanning answers that question. It sends connection-attempt packets to specific ports on each device and listens for responses that reveal which services are running.
In this step, get ready to:
- Write a tcp_scan() function that detects open ports using SYN packets.
- Integrate TCP scanning with your ARP discovery to scan all found hosts.
- Run the combined scanner and verify results.
Write the tcp_scan() function
A TCP SYN scan mimics the first step of the TCP three-way handshake. Your scanner sends a SYN packet to a port, and if the port is open, it responds with a SYN-ACK.
What is a TCP SYN scan?
When two computers want to communicate over TCP, they start with a three-step handshake: SYN, SYN-ACK, ACK. A SYN scan only sends the first packet (SYN) and watches for the second (SYN-ACK).
If the port is open, it replies with SYN-ACK (TCP flags 0x12). If the port is closed, it replies with RST (reset). If there is no reply at all, the port is likely filtered by a firewall.
- In recon_scanner.py, find the end of the arp_scan() function (the line return hosts).
- Below it, add the tcp_scan() function by copying this code:
def tcp_scan(target_ip, ports):
"""Scan target IP for open TCP ports using SYN packets."""
open_ports = []
packets = IP(dst=target_ip) / TCP(dport=ports, flags="S")
answered, _ = sr(packets, timeout=2, verbose=False)
for sent, received in answered:
if received[TCP].flags == 0x12:
open_ports.append(received[TCP].sport)
open_ports.sort()
return open_ports
What does this code do?
- IP(dst=target_ip) / TCP(dport=ports, flags="S") crafts a SYN packet for each port in the list, addressed to the target IP.
- sr() sends these packets at Layer 3 and collects responses. Unlike srp() (Layer 2 for ARP), sr() works at the IP layer.
- received[TCP].flags == 0x12 checks if the response has both the SYN and ACK flags set, which means the port is open and accepting connections.
- received[TCP].sport captures the source port from the response, which tells you which port number replied.
- Save recon_scanner.py.
You won't see a change yet because the function isn't being called. The next chunk connects it to your ARP scanner.
Seeing a syntax error on save?
- Make sure the function is placed between arp_scan() and display_results() with a blank line separating each function.
- Check that the indentation uses consistent spaces (4 spaces per level).
help me fix a syntax error in my tcp_scan function
Integrate TCP scanning with ARP discovery
Now you need code that ties both scanners together: discover hosts with ARP, then probe each host's ports with TCP.
- At the bottom of recon_scanner.py (below the display_results() function), add this execution code:
# Test code - will be replaced with argparse in the next step
if __name__ == "__main__":
target = [[TARGET_SUBNET="your local subnet e.g. 192.168.1.0/24"]]
ports = [22, 80, 443, 8080, 21, 53]
hosts = arp_scan(target)
if not hosts:
print("\n[-] No devices found. Check your target range and permissions.")
else:
print(f"\n[+] Found {len(hosts)} device(s). Running port scan...")
for host in hosts:
open_ports = tcp_scan(host["ip"], ports)
host["open_ports"] = open_ports
display_results(hosts, scan_ports=True)
What does this code do?
- ports = [22, 80, 443, 8080, 21, 53] defines the common service ports to check: SSH (22), HTTP (80), HTTPS (443), HTTP alternate (8080), FTP (21), and DNS (53).
- arp_scan(target) discovers all live devices first, giving you a list of IPs and MACs.
- The for loop calls tcp_scan() on each discovered host's IP and stores the results in the host dictionary under open_ports.
- display_results(hosts, scan_ports=True) tells your existing display function to also show the Open Ports column.
- Replace the target subnet value with your own local network range (the same one you used in the previous step).
- Save recon_scanner.py.
Find your subnet
If you're unsure of your subnet, most home networks use 192.168.1.0/24 or 192.168.0.0/24. You can check by running ipconfig in your terminal and looking at your IPv4 Address. Replace the last number with 0/24.
Run the combined scanner
Time to see both scanners working together. The script needs Administrator privileges because it sends raw packets.
- Confirm PyCharm is still running as Administrator (you set this up in the previous step).
- Run the script by clicking the green play button, or by running this in PyCharm's terminal:
python recon_scanner.py
The scan may take 10-30 seconds depending on how many devices are on your network.
- Verify the output shows a table with IP Address, MAC Address, and Open Ports columns.
- You should see at least your router listed. Your router likely has port 80 or 443 open for its admin interface.
What should I see?
Your output looks something like this:
[*] Starting ARP scan on 192.168.1.0/24...
[+] Found 4 device(s). Running port scan...
============================================================
IP Address MAC Address Open Ports
============================================================
192.168.1.1 aa:bb:cc:dd:ee:ff 80, 443
192.168.1.10 11:22:33:44:55:66 None found
192.168.1.15 77:88:99:aa:bb:cc None found
192.168.1.20 dd:ee:ff:00:11:22 8080
============================================================
[+] Scan complete. 4 device(s) found.
The exact devices and ports depend on your network. Seeing at least one device with an open port confirms everything is working.
No devices or ports found?
- Make sure PyCharm is running as Administrator. Right-click PyCharm in your taskbar and select Run as administrator.
- Double check your target subnet matches your actual network. Run ipconfig to confirm your IP address range.
- If you see devices but all show "None found" for ports, your network's firewall may be blocking SYN packets. Try scanning just your router's IP directly.
help me troubleshoot my network scanner not finding open ports
Why SYN scanning instead of a full TCP connection?
A full TCP connection completes the entire three-way handshake (SYN, SYN-ACK, ACK), which is slower and creates log entries on the target. SYN scanning only sends the initial SYN and reads the response, making it faster and stealthier.
This is the same technique that professional tools like Nmap use by default. By building it yourself with Scapy, you understand exactly what happens at the packet level.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file and your scan showed results.
ⓧ I'd like to double check the full code
Here is the complete recon_scanner.py at this point (function definitions only, excluding the temporary test code at the bottom):
from scapy.all import ARP, Ether, srp, IP, TCP, sr, conf
import argparse
import ipaddress
conf.verb = 0
def arp_scan(target):
"""Discover live hosts on a local subnet using ARP."""
print(f"\n[*] Starting ARP scan on {target}...")
arp_request = ARP(pdst=target)
broadcast = Ether(dst="ff:ff:ff:ff:ff:ff")
packet = broadcast / arp_request
answered, _ = srp(packet, timeout=3, verbose=False)
hosts = []
for sent, received in answered:
hosts.append({
"ip": received[ARP].psrc,
"mac": received[ARP].hwsrc
})
hosts.sort(key=lambda h: ipaddress.IPv4Address(h["ip"]))
return hosts
def tcp_scan(target_ip, ports):
"""Scan target IP for open TCP ports using SYN packets."""
open_ports = []
packets = IP(dst=target_ip) / TCP(dport=ports, flags="S")
answered, _ = sr(packets, timeout=2, verbose=False)
for sent, received in answered:
if received[TCP].flags == 0x12:
open_ports.append(received[TCP].sport)
open_ports.sort()
return open_ports
def display_results(hosts, scan_ports=None):
"""Display scan results in a formatted table."""
print("\n" + "=" * 60)
print(f"{'IP Address':<20}{'MAC Address':<25}{'Open Ports'}")
print("=" * 60)
for host in hosts:
ports_str = ""
if scan_ports and host.get("open_ports"):
ports_str = ", ".join(str(p) for p in host["open_ports"])
elif scan_ports:
ports_str = "None found"
print(f"{host['ip']:<20}{host['mac']:<25}{ports_str}")
print("=" * 60)
print(f"\n[+] Scan complete. {len(hosts)} device(s) found.")
Your scanner now discovers devices and identifies their running services in one sweep. Next up, you'll add command-line arguments so you can control the scan target and ports without editing the code.
Add Command-Line Arguments and Polish
In the previous step, you built a scanner that discovers devices and checks their open ports. Right now, the target IP range and port list are hardcoded inside the script.
Professional security tools accept command-line arguments so you can scan different networks without editing the source code. In this step, you will add argparse for flexible command-line flags and wrap everything in a main() function with proper error handling.
In this step, get ready to:
- Add a get_arguments() function to parse command-line flags.
- Add a main() function with error handling for common issues.
- Run the completed scanner from the command line with arguments.
Add command-line argument parsing
The argparse module (already imported in your file) lets you define flags that users pass when running the script. You will create a -t flag for the target IP range and a -p flag for custom port lists.
- In recon_scanner.py, scroll down below your display_results() function.
- Add the get_arguments() function by copying the following code below display_results():
def get_arguments():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(
description="Network Reconnaissance Scanner - Discover devices and open ports"
)
parser.add_argument(
"-t", "--target",
required=True,
help="Target IP range (e.g., 192.168.1.0/24)"
)
parser.add_argument(
"-p", "--ports",
default="22,80,443,8080,21,53",
help="Comma-separated list of ports to scan (default: 22,80,443,8080,21,53)"
)
return parser.parse_args()
What does this code do?
ArgumentParser creates a command-line interface for your script. It automatically generates help text and handles errors when users pass invalid arguments.
- -t / --target is marked required=True, so the script refuses to run without it.
- -p / --ports has a default value of common service ports, so users can skip it entirely.
- parse_args() reads the actual command-line input and returns the values in a namespace object.
- Save recon_scanner.py.
- You won't see a change yet. The next function will call get_arguments() and run the scanner.
Write the main function with error handling
The main() function ties everything together. It calls get_arguments(), runs both scans, and catches common errors like missing Administrator privileges or a missing Npcap installation.
- Below get_arguments(), add the main() function and the entry point guard by copying this code:
def main():
try:
args = get_arguments()
target = args.target
ports = [int(p.strip()) for p in args.ports.split(",")]
print("\n" + "=" * 60)
print(" NETWORK RECONNAISSANCE SCANNER")
print("=" * 60)
hosts = arp_scan(target)
if not hosts:
print("\n[-] No devices found. Check your target range and permissions.")
return
print(f"\n[+] Found {len(hosts)} device(s). Running port scan...")
for host in hosts:
open_ports = tcp_scan(host["ip"], ports)
host["open_ports"] = open_ports
display_results(hosts, scan_ports=True)
except PermissionError:
print("\n[!] Error: This script requires Administrator privileges.")
print(" Right-click your terminal and select 'Run as administrator'.")
except RuntimeError as e:
if "Npcap" in str(e) or "winpcap" in str(e).lower():
print("\n[!] Error: Npcap is not installed or not working correctly.")
print(" Download and install from: https://npcap.com")
else:
print(f"\n[!] Runtime error: {e}")
except KeyboardInterrupt:
print("\n\n[!] Scan interrupted by user.")
except Exception as e:
print(f"\n[!] Error: {e}")
if __name__ == "__main__":
main()
What does this code do?
The try block runs the happy path: parse arguments, run the ARP scan, run the TCP scan on each discovered host, then display results.
The except blocks catch specific failure modes:
- PermissionError fires when the script lacks Administrator privileges for raw packet access.
- RuntimeError with "Npcap" or "winpcap" in the message means the packet capture driver is missing.
- KeyboardInterrupt handles Ctrl+C gracefully so the learner sees a clean exit message.
- The generic Exception catch is a safety net for unexpected errors.
The if __name__ == "__main__" guard ensures main() only runs when you execute the script directly, not when it's imported as a module.
- Save recon_scanner.py.
Seeing a syntax error?
- Check that main() is at the same indentation level as your other function definitions (no extra indent).
- Make sure all the except blocks align with the try keyword.
- Verify you have matching quotes on all print statements. The f-strings use curly braces {len(hosts)} that must be inside the quotes.
Help me fix a syntax error in my recon_scanner.py main() function.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before running it.
ⓧ I'd like to double check the full code
Here is the complete recon_scanner.py file at this point:
from scapy.all import ARP, Ether, srp, IP, TCP, sr, conf
import argparse
import ipaddress
conf.verb = 0
def arp_scan(target):
"""Discover live hosts on a local subnet using ARP."""
print(f"\n[*] Starting ARP scan on {target}...")
arp_request = ARP(pdst=target)
broadcast = Ether(dst="ff:ff:ff:ff:ff:ff")
packet = broadcast / arp_request
answered, _ = srp(packet, timeout=3, verbose=False)
hosts = []
for sent, received in answered:
hosts.append({
"ip": received[ARP].psrc,
"mac": received[ARP].hwsrc
})
hosts.sort(key=lambda h: ipaddress.IPv4Address(h["ip"]))
return hosts
def tcp_scan(target_ip, ports):
"""Scan target IP for open TCP ports using SYN packets."""
open_ports = []
packets = IP(dst=target_ip) / TCP(dport=ports, flags="S")
answered, _ = sr(packets, timeout=2, verbose=False)
for sent, received in answered:
if received[TCP].flags == 0x12:
open_ports.append(received[TCP].sport)
open_ports.sort()
return open_ports
def display_results(hosts, scan_ports=None):
"""Display scan results in a formatted table."""
print("\n" + "=" * 60)
print(f"{'IP Address':<20}{'MAC Address':<25}{'Open Ports'}")
print("=" * 60)
for host in hosts:
ports_str = ""
if scan_ports and host.get("open_ports"):
ports_str = ", ".join(str(p) for p in host["open_ports"])
elif scan_ports:
ports_str = "None found"
print(f"{host['ip']:<20}{host['mac']:<25}{ports_str}")
print("=" * 60)
print(f"\n[+] Scan complete. {len(hosts)} device(s) found.")
def get_arguments():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(
description="Network Reconnaissance Scanner - Discover devices and open ports"
)
parser.add_argument(
"-t", "--target",
required=True,
help="Target IP range (e.g., 192.168.1.0/24)"
)
parser.add_argument(
"-p", "--ports",
default="22,80,443,8080,21,53",
help="Comma-separated list of ports to scan (default: 22,80,443,8080,21,53)"
)
return parser.parse_args()
def main():
try:
args = get_arguments()
target = args.target
ports = [int(p.strip()) for p in args.ports.split(",")]
print("\n" + "=" * 60)
print(" NETWORK RECONNAISSANCE SCANNER")
print("=" * 60)
hosts = arp_scan(target)
if not hosts:
print("\n[-] No devices found. Check your target range and permissions.")
return
print(f"\n[+] Found {len(hosts)} device(s). Running port scan...")
for host in hosts:
open_ports = tcp_scan(host["ip"], ports)
host["open_ports"] = open_ports
display_results(hosts, scan_ports=True)
except PermissionError:
print("\n[!] Error: This script requires Administrator privileges.")
print(" Right-click your terminal and select 'Run as administrator'.")
except RuntimeError as e:
if "Npcap" in str(e) or "winpcap" in str(e).lower():
print("\n[!] Error: Npcap is not installed or not working correctly.")
print(" Download and install from: https://npcap.com")
else:
print(f"\n[!] Runtime error: {e}")
except KeyboardInterrupt:
print("\n\n[!] Scan interrupted by user.")
except Exception as e:
print(f"\n[!] Error: {e}")
if __name__ == "__main__":
main()
Run the scanner from the command line
Your scanner is complete. Time to run it with command-line arguments and see the full output.
- Make sure PyCharm is running as Administrator (you set this up in Step 2).
- In PyCharm's terminal, run the scanner targeting your local subnet:
python recon_scanner.py -t [[SUBNET="your local subnet e.g. 192.168.1.0/24"]]
How do I find my subnet?
Open a separate terminal and run ipconfig. Look for your Wi-Fi or Ethernet adapter's IPv4 address (e.g. 192.168.1.105). Replace the last number with 0/24 to get your subnet (e.g. 192.168.1.0/24).
You should see output like this:
============================================================
NETWORK RECONNAISSANCE SCANNER
============================================================
[*] Starting ARP scan on 192.168.1.0/24...
[+] Found 3 device(s). Running port scan...
============================================================
IP Address MAC Address Open Ports
============================================================
192.168.1.1 aa:bb:cc:dd:ee:ff 80, 443
192.168.1.105 11:22:33:44:55:66 None found
192.168.1.110 77:88:99:aa:bb:cc None found
============================================================
[+] Scan complete. 3 device(s) found.
What should I see?
Your exact output will differ based on your network. You should see at least your router listed (usually the .1 address). Your router likely has port 80 or 443 open for its admin interface.
The scan may take 10-30 seconds depending on how many devices are on your network.
- Try the -h flag to see the auto-generated help text:
python recon_scanner.py -h
You should see a usage summary showing both the -t and -p flags with their descriptions.
Seeing a permissions error?
- If you see the message about Administrator privileges, close PyCharm and reopen it by right-clicking the PyCharm icon and selecting Run as administrator.
- If you see a message about Npcap, reinstall Npcap from https://npcap.com.
- If no devices are found, double-check your subnet range matches your actual network.
Help me troubleshoot my network scanner. It runs but I'm getting an error or no results.
Your network reconnaissance scanner is complete. You have a professional command-line tool that discovers devices and probes their open ports. Next up: the Secret Mission, where you will add an ICMP ping sweep mode and CSV export.
Secret mission
Add ICMP Ping Sweep and CSV Export
Your scanner already uses ARP and TCP. Real reconnaissance tools offer multiple scan modes and save results to files for later analysis. Can you add ICMP ping sweep as a third discovery technique and export your results to CSV?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources installed, pause them to come back later, or delete them entirely.
Resources you used:
- recon_scanner.py file (your scanner script).
- Any CSV output files generated during testing.
- Scapy 2.7.0 (Python package installed via pip).
- Npcap 1.88 (Windows packet capture driver).
Keep everything running
No action needed. Choose this if you plan to continue building network security tools with Scapy. Npcap stays installed as a system driver, and Scapy remains available in your Python environment.
Pause - I'll come back to this later
There are no running processes to stop. Your script only runs when you execute it manually. Everything stays on your system ready for when you return.
No action required for pausing. Your files, Scapy, and Npcap will all be waiting for you.
Delete - I don't want to use this again
Remove all project resources and installed tools. Follow these steps to clean up completely.
- Delete your project files by opening PyCharm's terminal and running:
- Delete recon_scanner.py and any CSV output files from your project folder.
del recon_scanner.py
del *.csv
- Uninstall Scapy by running this command in your terminal:
pip uninstall scapy
Type y when prompted to confirm the uninstall.
- Uninstall Npcap through Windows Settings:
- Open Settings on your computer.
- Select Apps, then Apps & features.
- Search for Npcap in the list.
- Click Uninstall and follow the prompts to remove it.
- Restart your computer if prompted.
Nice Work!
Nice Work!
Nice work! You just built a network reconnaissance scanner from scratch using Python and Scapy. This is the same packet-crafting approach that professional penetration testers use at the start of every engagement.
You've learned how to:
- Craft raw ARP packets to discover live devices on your local network, collecting their IP and MAC addresses using Layer 2 broadcast requests.
- Implement TCP SYN scanning to detect open ports by sending SYN packets and interpreting SYN-ACK responses, without completing the full TCP handshake.
- Use argparse to build a professional command-line interface with flags for target ranges, port lists, and scan modes.
- Secret Mission: Added ICMP ping sweep functionality and CSV export for professional-quality scan reports.
Ready to quiz yourself?