Build a Network Toolkit in Python
Build a subnet calculator and ping monitor CLI tool in Python.
Introduction
30 Second Summary
Every device on the internet has an address, just like every house on a street. Understanding how these addresses are organized into networks is the foundation of everything from setting up a home router to managing enterprise infrastructure.
In this project, you will build a Python command-line network toolkit that calculates subnet information from CIDR notation and monitors host reachability with parallel ping sweeps.
What You'll Build
A Python CLI application with two modes: a subnet calculator that breaks down CIDR notation into network properties, and a ping monitor that tests multiple hosts in parallel using ICMP.
By the end of this project, you'll have:
- A subnet calculator that converts any CIDR notation into network address, broadcast address, subnet mask, and usable host range.
- A ping monitor that checks reachability of multiple hosts simultaneously and reports response times.
- A unified network toolkit CLI that ties both tools together with a combined subnet-and-ping mode for real-world network troubleshooting.
- Secret Mission: A subnet splitter that divides a network into N equal subnets and displays the allocation table.
Do I need networking experience?
No prior networking knowledge is required. You should be comfortable with basic Python (variables, functions, loops, and if statements). This project teaches you networking concepts hands-on as you build each tool.
Before We Start
Before you write any networking code, you need a workspace where everything runs smoothly. A proper setup now means zero friction later when you're parsing subnets and pinging hosts.
In this step, you will install VS Code with the Python extension, create your project folder, and set up the three files that will become your network toolkit.
In this step, get ready to:
- Install VS Code and the Python extension.
- Create the project folder and verify Python works.
- Create three empty Python files for the toolkit.
Install VS Code
- Download VS Code by visiting this link:
- https://code.visualstudio.com/Download
- Run the installer once it finishes downloading.
- Accept the default settings and click through the installation wizard.
What does the installer do?
The VS Code installer places the application in your user folder and adds the code command to your system PATH.
You will use the code command later to open your project folder directly from the terminal.
- Press the Windows key to open your search bar.
- Type Visual Studio Code and press Enter to open it.
VS Code opens to a Welcome tab. Now install the Python extension so VS Code understands your code.
- Click the Extensions icon in the left sidebar (it looks like four small squares).
- Type Python in the search bar at the top of the Extensions panel.
- Find the extension named Python by Microsoft and click Install.
Verify it installed
After clicking Install, the button changes to a gear icon. You should also see a small Python label in the bottom-left status bar when you open a .py file later.
Don't see the Extensions panel?
Make sure you clicked the icon with four squares on the left sidebar. You can also press Ctrl+Shift+X to open it directly.
help me find the Extensions panel in VS Code
Create the project folder
You need a dedicated folder on your Desktop to hold all three toolkit files. You will create it from Windows Terminal and then open it in VS Code.
- Press the Windows key to open your search bar.
- Type Terminal and press Enter to open Windows Terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir network-toolkit
cd network-toolkit
- Open this folder in VS Code by running:
code .
VS Code reopens with network-toolkit as the active workspace. You should see the folder name in the top title bar.
Seeing 'code' is not recognized?
This happens when the terminal was opened before VS Code finished installing. Close Windows Terminal completely, reopen it, and try code . again.
help me fix the code command not being recognized in my terminal
Now verify that Python is available inside VS Code's terminal.
- Open the integrated terminal inside VS Code by pressing Ctrl+` (backtick key, usually above Tab).
- Verify Python works by running this command:
python --version
You should see Python 3.14.5 printed in the terminal output.
Seeing a different version or an error?
If you see a version lower than 3.14, your PATH may point to an older Python installation. Search for "Manage app execution aliases" in Windows Settings and disable the Python aliases, then try again.
If you see "python is not recognized", Python may not be on your PATH. Reinstall Python from python.org and check the "Add to PATH" option during installation.
help me get Python 3.14 working in my VS Code terminal
Create the project files
Your toolkit will live across three files. Each one handles a different part of the tool.
- Create the three project files by running these commands in the VS Code terminal:
New-Item subnet_calculator.py
New-Item ping_monitor.py
New-Item network_toolkit.py
What are these files for?
subnet_calculator.py will break down CIDR notation into network properties. ping_monitor.py will test whether hosts are reachable. network_toolkit.py will combine both tools into one menu-driven CLI.
- Confirm the files exist by running:
ls
You should see subnet_calculator.py, ping_monitor.py, and network_toolkit.py listed in the output.
Files not showing up?
Make sure you are inside the network-toolkit folder. Run pwd to check your current location. If you are somewhere else, run cd ~/Desktop/network-toolkit first.
help me find my project files in the terminal
You should also see all three files appear in the VS Code Explorer sidebar on the left.
Your workspace is ready. Next up, you will build the subnet calculator that breaks down any IP address into its network components.
Set Up Your Development Environment
Building a network toolkit requires three specialized Python files working together. Each file handles a distinct responsibility: one for subnet calculations, one for ping monitoring, and one to tie everything into a unified menu.
Your development environment is already set up with VS Code and Python. In this step, you'll confirm your project files are in place and get them ready for editing.
In this step, get ready to:
- Confirm your three project files are present in the project folder.
- Open all three files for editing in VS Code.
Confirm your project files are present
Your project folder should contain three Python files, each corresponding to a different module of the network toolkit.
- In the VS Code terminal, confirm all three files are present by running this command:
dir
What should I see?
You should see three files listed in the output:
- subnet_calculator.py
- ping_monitor.py
- network_toolkit.py
What does each file do?
subnet_calculator.py handles all the subnet math. It takes CIDR notation like 192.168.1.0/24 and breaks it down into network properties.
ping_monitor.py handles host reachability testing. It sends ICMP ping requests to check which hosts are up or down.
network_toolkit.py ties both tools together into a single menu-driven interface.
Don't see all three files?
Make sure your terminal is in the correct project folder. Check the path shown in your terminal prompt matches the folder open in VS Code's Explorer sidebar.
If a file is missing, right-click in the Explorer sidebar, select New File, and type the exact filename (e.g. subnet_calculator.py).
help me find my project files in VS Code
Open the files in the editor
Opening each file as a tab in VS Code lets you switch between them quickly as you build out each module.
- In the Explorer sidebar on the left, click subnet_calculator.py to open it in the editor.
- Click ping_monitor.py to open it in a second tab.
- Click network_toolkit.py to open it in a third tab.
You should now see three tabs along the top of the editor area, one for each file.
Pin your tabs
Right-click any tab and select Pin to prevent it from being replaced when you open other files. This keeps your three core files always accessible.
Your workspace is locked in and ready to go. Next up, you'll write the subnet calculator logic that turns CIDR notation into a full network breakdown.
Build the Subnet Calculator
Your development environment is ready. Now it's time to build the first tool in your network toolkit: a subnet calculator.
Every network engineer needs to know how to break a CIDR notation like 192.168.1.0/24 into its component parts. Your calculator will take any CIDR input and instantly return the network address, broadcast address, subnet mask, and usable host range.
In this step, get ready to:
- Write a subnet analysis function using Python's built-in ipaddress module.
- Build a formatted display function that handles edge cases and errors.
- Add an interactive CLI loop so users can calculate multiple subnets.
Write the subnet analysis function
Python includes a built-in ipaddress module that handles all the binary math of subnetting for you. This is the same module that professional network automation tools use under the hood.
- In VS Code, click on the subnet_calculator.py tab to open it.
- Add the following code to build the subnet analysis function:
import ipaddress
import sys
def analyze_subnet(cidr_string):
"""Analyze a CIDR notation and return all subnet details."""
try:
network = ipaddress.IPv4Network(cidr_string, strict=False)
except ValueError as e:
return {"error": str(e)}
hosts = list(network.hosts())
total_addresses = network.num_addresses
usable_hosts = len(hosts)
result = {
"input": cidr_string,
"network_address": str(network.network_address),
"broadcast_address": str(network.broadcast_address),
"netmask": str(network.netmask),
"wildcard_mask": str(network.hostmask),
"prefix_length": network.prefixlen,
"total_addresses": total_addresses,
"usable_hosts": usable_hosts,
"first_host": str(hosts[0]) if hosts else "N/A",
"last_host": str(hosts[-1]) if hosts else "N/A",
"is_private": network.is_private,
}
# Special case notes
if network.prefixlen == 32:
result["note"] = "Single host address (no network range)"
elif network.prefixlen == 31:
result["note"] = "Point-to-point link (RFC 3021, both addresses usable)"
elif network.prefixlen == 0:
result["note"] = "Default route (entire IPv4 address space)"
return result
What does this code do?
This function takes a CIDR string like 192.168.1.0/24 and extracts every useful property from it.
- IPv4Network(cidr_string, strict=False) parses the CIDR notation. The strict=False parameter means the function accepts inputs where host bits are set (like 192.168.1.5/24) and automatically corrects to the network address.
- network.hosts() returns all usable host addresses, excluding the network and broadcast addresses.
- The edge case handling adds context notes for special prefix lengths: /32 (single host), /31 (point-to-point link), and /0 (the entire internet).
What is CIDR notation?
CIDR stands for Classless Inter-Domain Routing. The number after the slash (like /24) tells you how many bits of the address are the network portion.
A /24 means the first 24 bits are the network, leaving 8 bits for host addresses. That gives you 256 total addresses (2^8), with 254 usable for hosts.
- Save subnet_calculator.py.
You won't see a change yet. The function returns a dictionary of results, but we still need a display function to format the output.
Handle edge cases and display output
The analysis function returns a dictionary, but users need a readable table. The display function also needs to handle error cases gracefully.
- In subnet_calculator.py, add the following function below analyze_subnet:
def display_results(result):
"""Display subnet analysis results in a formatted table."""
if "error" in result:
print(f"\n [ERROR] Invalid input: {result['error']}")
print(" Tip: Use CIDR notation like 192.168.1.0/24")
return
print("\n" + "=" * 50)
print(f" Subnet Analysis: {result['input']}")
print("=" * 50)
print(f" Network Address: {result['network_address']}")
print(f" Broadcast Address: {result['broadcast_address']}")
print(f" Netmask: {result['netmask']}")
print(f" Wildcard Mask: {result['wildcard_mask']}")
print(f" Prefix Length: /{result['prefix_length']}")
print(f" Total Addresses: {result['total_addresses']}")
print(f" Usable Hosts: {result['usable_hosts']}")
print(f" First Usable Host: {result['first_host']}")
print(f" Last Usable Host: {result['last_host']}")
print(f" Private Network: {'Yes' if result['is_private'] else 'No'}")
if "note" in result:
print(f" Note: {result['note']}")
print("=" * 50)
What does this code do?
- The function first checks if the result dictionary contains an "error" key. If it does, it prints a helpful error message and exits early.
- For valid results, it prints a formatted table with all the subnet properties lined up in columns.
- The conditional if "note" in result only displays edge case notes when they apply (for /32, /31, or /0 networks).
- Save subnet_calculator.py.
You won't see output yet. The next chunk adds the interactive loop that ties everything together.
Add the interactive CLI loop
The calculator needs a way for users to enter CIDR notations and see results. A while True loop keeps the program running until the user types quit.
- In subnet_calculator.py, add the following code below display_results:
def main():
"""Interactive subnet calculator CLI."""
print("\n--- Subnet Calculator ---")
print("Enter a CIDR notation (e.g., 192.168.1.0/24) or 'quit' to exit.\n")
while True:
user_input = input("Enter CIDR: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
print("Goodbye!")
break
if not user_input:
print(" Please enter a valid CIDR notation.\n")
continue
result = analyze_subnet(user_input)
display_results(result)
print()
if __name__ == "__main__":
main()
What does this code do?
- The while True loop keeps prompting the user until they type quit, exit, or q.
- Empty input is caught and re-prompts without crashing.
- The if __name__ == "__main__" guard ensures main() only runs when you execute the file directly. Later, other scripts can import functions from this file without triggering the interactive loop.
- Save subnet_calculator.py.
- In the VS Code terminal, run your script:
python subnet_calculator.py
- When prompted, type 10.0.0.0/24 and press Enter.
- You should see a table showing Network: 10.0.0.0, Broadcast: 10.0.0.255, Netmask: 255.255.255.0, and Usable Hosts: 254.
- Type quit to exit the calculator.
Seeing a syntax error or no output?
- Check that you saved subnet_calculator.py before running the command. Unsaved changes won't execute.
- Make sure you typed the CIDR notation with a forward slash, not a backslash: 10.0.0.0/24 not 10.0.0.0\24.
- Verify that your terminal is in the correct folder. Run ls and confirm you see subnet_calculator.py in the output.
Why isn't my subnet calculator running correctly?
✔️ 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
Here is the complete subnet_calculator.py file:
import ipaddress
import sys
def analyze_subnet(cidr_string):
"""Analyze a CIDR notation and return all subnet details."""
try:
network = ipaddress.IPv4Network(cidr_string, strict=False)
except ValueError as e:
return {"error": str(e)}
hosts = list(network.hosts())
total_addresses = network.num_addresses
usable_hosts = len(hosts)
result = {
"input": cidr_string,
"network_address": str(network.network_address),
"broadcast_address": str(network.broadcast_address),
"netmask": str(network.netmask),
"wildcard_mask": str(network.hostmask),
"prefix_length": network.prefixlen,
"total_addresses": total_addresses,
"usable_hosts": usable_hosts,
"first_host": str(hosts[0]) if hosts else "N/A",
"last_host": str(hosts[-1]) if hosts else "N/A",
"is_private": network.is_private,
}
# Special case notes
if network.prefixlen == 32:
result["note"] = "Single host address (no network range)"
elif network.prefixlen == 31:
result["note"] = "Point-to-point link (RFC 3021, both addresses usable)"
elif network.prefixlen == 0:
result["note"] = "Default route (entire IPv4 address space)"
return result
def display_results(result):
"""Display subnet analysis results in a formatted table."""
if "error" in result:
print(f"\n [ERROR] Invalid input: {result['error']}")
print(" Tip: Use CIDR notation like 192.168.1.0/24")
return
print("\n" + "=" * 50)
print(f" Subnet Analysis: {result['input']}")
print("=" * 50)
print(f" Network Address: {result['network_address']}")
print(f" Broadcast Address: {result['broadcast_address']}")
print(f" Netmask: {result['netmask']}")
print(f" Wildcard Mask: {result['wildcard_mask']}")
print(f" Prefix Length: /{result['prefix_length']}")
print(f" Total Addresses: {result['total_addresses']}")
print(f" Usable Hosts: {result['usable_hosts']}")
print(f" First Usable Host: {result['first_host']}")
print(f" Last Usable Host: {result['last_host']}")
print(f" Private Network: {'Yes' if result['is_private'] else 'No'}")
if "note" in result:
print(f" Note: {result['note']}")
print("=" * 50)
def main():
"""Interactive subnet calculator CLI."""
print("\n--- Subnet Calculator ---")
print("Enter a CIDR notation (e.g., 192.168.1.0/24) or 'quit' to exit.\n")
while True:
user_input = input("Enter CIDR: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
print("Goodbye!")
break
if not user_input:
print(" Please enter a valid CIDR notation.\n")
continue
result = analyze_subnet(user_input)
display_results(result)
print()
if __name__ == "__main__":
main()
Your subnet calculator is working. Next up, you'll build a ping monitor that tests whether hosts are reachable across the network.
Build the Ping Monitor
Your subnet calculator can tell you everything about a network on paper. It shows the address range, the mask, and the host count.
But it cannot tell you if those hosts are actually alive and reachable. In this step, you will build a ping monitor that sends ICMP echo requests to multiple hosts in parallel, then reports which ones are up and how fast they respond.
In this step, get ready to:
- Write a function that pings a single host and parses the response time.
- Add parallel execution to ping multiple hosts simultaneously.
- Build an interactive CLI with default targets and a repeat option.
Write the single-host ping function
Before you can monitor many hosts, you need a function that pings one host and returns a structured result. On Windows, the system ping command sends an ICMP packet and prints the reply time.
Your function will call the Windows ping command using Python's subprocess module, then parse the text output to extract the response time in milliseconds.
- In VS Code, open ping_monitor.py from the Explorer sidebar.
- Add the imports and the ping_host() function by pasting this code:
import subprocess
import time
from concurrent.futures import ThreadPoolExecutor
def ping_host(host, timeout_ms=2000):
"""Ping a single host and return the result."""
host = host.strip()
if not host:
return {"host": host, "status": "Invalid", "time_ms": None}
try:
result = subprocess.run(
["ping", "-n", "1", "-w", str(timeout_ms), host],
capture_output=True,
text=True,
timeout=10,
)
output = result.stdout
if result.returncode == 0 and "time=" in output.lower():
# Extract response time from output
for line in output.split("\n"):
if "time=" in line.lower():
# Windows format: "Reply from x.x.x.x: bytes=32 time=14ms TTL=117"
time_part = line.lower().split("time=")[1]
time_value = time_part.split("ms")[0].strip("<")
try:
ms = float(time_value)
except ValueError:
ms = 0.0
return {"host": host, "status": "Up", "time_ms": ms}
return {"host": host, "status": "Up", "time_ms": 0.0}
else:
return {"host": host, "status": "Down", "time_ms": None}
except subprocess.TimeoutExpired:
return {"host": host, "status": "Timeout", "time_ms": None}
except Exception as e:
return {"host": host, "status": "Error", "time_ms": None}
What does this code do?
The imports bring in three standard library modules you will use throughout this file. subprocess runs external commands, time measures elapsed seconds, and ThreadPoolExecutor runs tasks in parallel.
The ping_host() function calls the Windows ping command with -n 1 (send one packet) and -w 2000 (wait up to 2000 milliseconds for a reply). It captures the text output and searches for time= in the reply to extract the response time.
The .strip("<") handles Windows responses that say time<1ms for very fast replies on the local network.
- Save ping_monitor.py.
- In the VS Code terminal, run the file to check for syntax errors:
python ping_monitor.py
- You should see no output and no errors. The script exits silently because there is no main() function yet.
Seeing a SyntaxError?
Check that every opening parenthesis, bracket, and quote has a matching close. The subprocess.run() call spans multiple lines, so a missing comma or parenthesis is the most common culprit.
help me fix a SyntaxError in my ping_monitor.py file
Add parallel multi-host pinging
Pinging hosts one at a time is slow. If you have 10 hosts and each takes 2 seconds to time out, that is 20 seconds of waiting. A ThreadPoolExecutor lets you ping all of them at the same time and collect results as they come in.
- Below the ping_host() function, add the ping_multiple_hosts() function:
def ping_multiple_hosts(hosts, timeout_ms=2000, max_workers=10):
"""Ping multiple hosts in parallel and return results."""
results = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(ping_host, host, timeout_ms): host for host in hosts}
for future in futures:
results.append(future.result())
return results
What does this code do?
The ThreadPoolExecutor creates a pool of up to 10 worker threads. Each thread runs ping_host() for one host simultaneously. Instead of waiting 2 seconds per host sequentially, all 10 hosts finish in roughly 2 seconds total.
The futures dictionary maps each submitted task to its host name. Iterating over it and calling .result() blocks until that specific ping completes, then appends the result to the list.
You won't see a change yet. The next function formats these results into a readable table.
- Below ping_multiple_hosts(), add the display_results() function:
def display_results(results):
"""Display ping results in a formatted table."""
print("\n" + "=" * 55)
print(f" {'Host':<25} {'Status':<10} {'Time':<10}")
print("-" * 55)
for r in results:
status_marker = "[UP] " if r["status"] == "Up" else "[DOWN]"
time_display = f"{r['time_ms']:.1f}ms" if r["time_ms"] is not None else "---"
print(f" {r['host']:<25} {status_marker:<10} {time_display:<10}")
print("=" * 55)
up_count = sum(1 for r in results if r["status"] == "Up")
total = len(results)
print(f" Summary: {up_count}/{total} hosts reachable")
print()
What does this code do?
This function prints a formatted table with three columns: Host, Status, and Time. Each row shows [UP] or [DOWN] as a quick visual indicator. The :<25 syntax left-aligns text in a 25-character-wide column so everything lines up neatly.
At the bottom, a summary line counts how many hosts responded versus the total tested.
- Save ping_monitor.py.
- You won't see output yet. The next substep adds the main() function that ties everything together.
Add the interactive CLI
Your ping and display functions are ready. Now you need an interactive loop that asks the user for hosts, calls the ping functions, and offers a repeat option for monitoring intermittent connections.
- Below display_results(), add the main() function and the entry point:
def main():
"""Interactive ping monitor CLI."""
print("\n--- Ping Monitor ---")
print("Enter hosts to ping (comma-separated) or 'quit' to exit.")
print("Press Enter with no input to ping default targets (8.8.8.8, 1.1.1.1).\n")
default_hosts = ["8.8.8.8", "1.1.1.1", "192.168.1.1"]
while True:
user_input = input("Hosts to ping: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
print("Goodbye!")
break
if not user_input:
hosts = default_hosts
print(f" Using defaults: {', '.join(hosts)}")
else:
hosts = [h.strip() for h in user_input.split(",") if h.strip()]
if not hosts:
print(" No valid hosts entered.\n")
continue
print(" Pinging... please wait.")
start_time = time.time()
results = ping_multiple_hosts(hosts)
elapsed = time.time() - start_time
display_results(results)
print(f" Completed in {elapsed:.1f} seconds.")
again = input(" Ping again? (y/n): ").strip().lower()
if again != "y":
print()
if __name__ == "__main__":
main()
What does this code do?
The main() function runs a while True loop that prompts for comma-separated hostnames or IP addresses. If the user presses Enter without typing anything, it uses three default targets: Google DNS (8.8.8.8), Cloudflare DNS (1.1.1.1), and a common local gateway (192.168.1.1).
After displaying results, it measures the total elapsed time and asks if the user wants to ping again. This repeat feature is useful for monitoring flaky connections over time.
- Save ping_monitor.py.
- Test your ping monitor by running:
python ping_monitor.py
- When prompted, type 8.8.8.8, 1.1.1.1 and press Enter.
- You should see a table with both hosts showing [UP] status and response times in milliseconds.
- Type n at the "Ping again?" prompt, then type quit to exit.
Seeing [DOWN] for all hosts?
Some corporate networks and VPNs block outgoing ICMP traffic. Try disconnecting from your VPN, or ping a host on your local network (like your router at 192.168.1.1) to confirm the tool works.
If you see a "Timeout" status instead of "Down", the host is reachable but responding too slowly. This is expected for distant or congested servers.
help me troubleshoot why my ping monitor shows all hosts as DOWN
✔️ Awesome, I've got everything!
Great. Double check you have saved ping_monitor.py and both Google DNS and Cloudflare DNS show as [UP] in your output.
ⓧ I'd like to double check the full code
Here is the complete ping_monitor.py file. Make sure yours matches exactly:
import subprocess
import time
from concurrent.futures import ThreadPoolExecutor
def ping_host(host, timeout_ms=2000):
"""Ping a single host and return the result."""
host = host.strip()
if not host:
return {"host": host, "status": "Invalid", "time_ms": None}
try:
result = subprocess.run(
["ping", "-n", "1", "-w", str(timeout_ms), host],
capture_output=True,
text=True,
timeout=10,
)
output = result.stdout
if result.returncode == 0 and "time=" in output.lower():
# Extract response time from output
for line in output.split("\n"):
if "time=" in line.lower():
# Windows format: "Reply from x.x.x.x: bytes=32 time=14ms TTL=117"
time_part = line.lower().split("time=")[1]
time_value = time_part.split("ms")[0].strip("<")
try:
ms = float(time_value)
except ValueError:
ms = 0.0
return {"host": host, "status": "Up", "time_ms": ms}
return {"host": host, "status": "Up", "time_ms": 0.0}
else:
return {"host": host, "status": "Down", "time_ms": None}
except subprocess.TimeoutExpired:
return {"host": host, "status": "Timeout", "time_ms": None}
except Exception as e:
return {"host": host, "status": "Error", "time_ms": None}
def ping_multiple_hosts(hosts, timeout_ms=2000, max_workers=10):
"""Ping multiple hosts in parallel and return results."""
results = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(ping_host, host, timeout_ms): host for host in hosts}
for future in futures:
results.append(future.result())
return results
def display_results(results):
"""Display ping results in a formatted table."""
print("\n" + "=" * 55)
print(f" {'Host':<25} {'Status':<10} {'Time':<10}")
print("-" * 55)
for r in results:
status_marker = "[UP] " if r["status"] == "Up" else "[DOWN]"
time_display = f"{r['time_ms']:.1f}ms" if r["time_ms"] is not None else "---"
print(f" {r['host']:<25} {status_marker:<10} {time_display:<10}")
print("=" * 55)
up_count = sum(1 for r in results if r["status"] == "Up")
total = len(results)
print(f" Summary: {up_count}/{total} hosts reachable")
print()
def main():
"""Interactive ping monitor CLI."""
print("\n--- Ping Monitor ---")
print("Enter hosts to ping (comma-separated) or 'quit' to exit.")
print("Press Enter with no input to ping default targets (8.8.8.8, 1.1.1.1).\n")
default_hosts = ["8.8.8.8", "1.1.1.1", "192.168.1.1"]
while True:
user_input = input("Hosts to ping: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
print("Goodbye!")
break
if not user_input:
hosts = default_hosts
print(f" Using defaults: {', '.join(hosts)}")
else:
hosts = [h.strip() for h in user_input.split(",") if h.strip()]
if not hosts:
print(" No valid hosts entered.\n")
continue
print(" Pinging... please wait.")
start_time = time.time()
results = ping_multiple_hosts(hosts)
elapsed = time.time() - start_time
display_results(results)
print(f" Completed in {elapsed:.1f} seconds.")
again = input(" Ping again? (y/n): ").strip().lower()
if again != "y":
print()
if __name__ == "__main__":
main()
Why subprocess instead of raw sockets?
Sending ICMP packets directly with raw sockets on Windows requires Administrator privileges and complex socket configuration. Using subprocess to call the system ping command works without elevation and teaches you how to interact with OS networking tools.
This is the same approach real automation scripts use. The skill of parsing command output transfers to any CLI tool you need to integrate with Python.
Your ping monitor is complete. Next up, you will combine both tools into a unified network toolkit with a menu-driven interface and a combined subnet-then-ping workflow.
Combine Into the Network Toolkit
You now have two standalone tools that each handle one networking task. In real-world workflows, network engineers use a single tool that covers multiple operations. Switching between separate scripts is clunky.
In this step, you will combine both tools into one unified CLI with a menu system. You will also add a powerful combined mode that calculates a subnet and then pings hosts inside it.
In this step, get ready to:
- Build the unified CLI menu that imports and routes to both tools.
- Add a combined mode that analyzes a subnet and pings sample hosts in it.
- Add educational summary output that explains networking concepts after each operation.
Build the unified CLI menu
The toolkit file imports core functions from your other two modules and wraps them in a menu-driven interface. This lets you run everything from a single command.
- In VS Code, open network_toolkit.py (currently empty).
- Add the imports and menu display function by pasting this code:
import ipaddress
from subnet_calculator import analyze_subnet, display_results as display_subnet
from ping_monitor import ping_multiple_hosts, display_results as display_ping
def show_menu():
"""Display the main menu."""
print("\n" + "=" * 40)
print(" NETWORK TOOLKIT")
print("=" * 40)
print(" [1] Subnet Calculator")
print(" [2] Ping Monitor")
print(" [3] Subnet + Ping (combined)")
print(" [4] Quit")
print("=" * 40)
What does this code do?
The from ... import ... as ... lines pull specific functions out of your other files. Both modules have a function called display_results, so you rename them with as to avoid a naming collision.
The show_menu() function prints the four options the user can choose from each time the main loop cycles.
- Next, add the subnet calculator wrapper function below show_menu() by pasting this code:
def subnet_calculator_mode():
"""Run the subnet calculator interactively."""
print("\n--- Subnet Calculator ---")
print("Enter CIDR notation or 'back' to return to menu.\n")
while True:
user_input = input("Enter CIDR: ").strip()
if user_input.lower() in ("back", "b"):
break
if not user_input:
continue
result = analyze_subnet(user_input)
display_subnet(result)
What does this code do?
This function wraps the subnet calculator in a loop that keeps accepting CIDR inputs until the user types back. It calls analyze_subnet() and display_subnet() from your existing subnet_calculator.py module.
- Add the ping monitor wrapper function below subnet_calculator_mode() by pasting this code:
def ping_monitor_mode():
"""Run the ping monitor interactively."""
print("\n--- Ping Monitor ---")
print("Enter hosts (comma-separated) or 'back' to return to menu.")
print("Press Enter for defaults (8.8.8.8, 1.1.1.1).\n")
while True:
user_input = input("Hosts to ping: ").strip()
if user_input.lower() in ("back", "b"):
break
if not user_input:
hosts = ["8.8.8.8", "1.1.1.1"]
print(f" Using defaults: {', '.join(hosts)}")
else:
hosts = [h.strip() for h in user_input.split(",") if h.strip()]
if not hosts:
continue
print(" Pinging... please wait.")
results = ping_multiple_hosts(hosts)
display_ping(results)
What does this code do?
This wrapper calls ping_multiple_hosts() from your existing ping_monitor.py module. It provides default targets so the user can just press Enter for a quick connectivity check.
- Add the main entry point and the if __name__ guard at the very bottom of the file by pasting this code:
def main():
"""Main entry point for the network toolkit."""
print("\nWelcome to the Network Toolkit!")
print("A hands-on tool for learning networking fundamentals.\n")
while True:
show_menu()
choice = input("\n Select an option (1-4): ").strip()
if choice == "1":
subnet_calculator_mode()
elif choice == "2":
ping_monitor_mode()
elif choice == "3":
combined_mode()
elif choice == "4":
print("\nGoodbye! Keep exploring networks.")
break
else:
print(" Invalid choice. Please enter 1, 2, 3, or 4.")
if __name__ == "__main__":
main()
What does this code do?
The main() function runs a while True loop that displays the menu and routes the user to the chosen tool. Option 3 calls combined_mode() which you will write next.
The if __name__ == "__main__" guard ensures main() only runs when you execute the file directly, not when another script imports from it.
- Save network_toolkit.py.
You won't run the toolkit yet because combined_mode() is not defined. You will add it in the next substep.
Seeing a red underline on combined_mode?
Your editor may show a warning because combined_mode() does not exist yet. This is expected. The warning will disappear once you add the function in the next substep.
If you see errors on the import lines, make sure all three Python files are in the same folder.
Help me fix import errors in my network toolkit.
Add a combined subnet and ping mode
The combined mode demonstrates a real-world workflow. First you figure out what network you are dealing with, then you test whether hosts in that network are reachable.
Why restrict to private networks?
Scanning public IP ranges you do not own can trigger abuse reports or violate your ISP's terms of service. The combined mode uses the is_private property from Python's ipaddress module to only allow pinging RFC 1918 private ranges (10.x, 172.16-31.x, 192.168.x).
- In network_toolkit.py, add the combined_mode() function between ping_monitor_mode() and main() by pasting this code:
def combined_mode():
"""Subnet calculation followed by pinging hosts in that subnet."""
print("\n--- Subnet + Ping ---")
print("Enter a CIDR notation to analyze and then ping key hosts.")
print("Only private networks (10.x, 172.16-31.x, 192.168.x) are allowed.\n")
user_input = input("Enter CIDR: ").strip()
if not user_input:
return
result = analyze_subnet(user_input)
if "error" in result:
display_subnet(result)
return
display_subnet(result)
# Safety check: only allow pinging private networks
try:
network = ipaddress.IPv4Network(user_input, strict=False)
except ValueError:
return
if not network.is_private:
print("\n [SAFETY] This is a public network. Ping sweep skipped.")
print(" Only scan networks you own or have permission to test.")
return
hosts = list(network.hosts())
if not hosts:
print("\n No usable hosts in this network to ping.")
return
# Pick a sample: first, last, and a middle host
sample_hosts = []
sample_hosts.append(str(hosts[0]))
if len(hosts) > 2:
sample_hosts.append(str(hosts[len(hosts) // 2]))
if len(hosts) > 1:
sample_hosts.append(str(hosts[-1]))
print(f"\n Pinging sample hosts: {', '.join(sample_hosts)}")
print(" (First, middle, and last usable host in the subnet)")
print(" Pinging... please wait.")
results = ping_multiple_hosts(sample_hosts)
display_ping(results)
What does this code do?
The function first analyzes the subnet, then checks whether it is a private network using network.is_private. If the network is public, it refuses to ping and exits early.
For private networks, it selects three sample hosts (first, middle, and last usable address) and pings them in parallel. This gives you a quick reachability snapshot without scanning the entire subnet.
- Save network_toolkit.py.
The editor warning on combined_mode in main() should now disappear since the function exists.
Still seeing a warning on combined_mode?
Make sure you pasted the function above main(), not inside it. The function should be at the same indentation level as ping_monitor_mode().
Help me fix the placement of combined_mode in my toolkit.
Add summary output after each operation
Each mode should teach the user something about networking after displaying results. You will add short concept explanations that appear after every calculation or ping.
- In network_toolkit.py, find this line inside subnet_calculator_mode():
result = analyze_subnet(user_input)
display_subnet(result)
- Add the following lines directly below display_subnet(result), still inside the while True loop:
print("\n [Concept] The broadcast address is used to send")
print(" a message to ALL hosts on this network simultaneously.")
print()
- Now find this line inside ping_monitor_mode():
results = ping_multiple_hosts(hosts)
display_ping(results)
- Add the following lines directly below display_ping(results), still inside the while True loop:
print(" [Concept] Ping uses ICMP (Internet Control Message Protocol).")
print(" Some hosts block ICMP, so 'Down' may mean the firewall")
print(" is dropping packets, not that the host is offline.")
print()
Why add concept notes?
These short explanations turn your toolkit into a learning tool. After calculating a subnet, the user learns what the broadcast address does. After pinging, they learn why ICMP packets might be blocked by firewalls.
- Save network_toolkit.py.
- Run the toolkit by entering this command in the VS Code terminal:
python network_toolkit.py
- You should see the NETWORK TOOLKIT menu with options [1] through [4].
- Type 1 and press Enter to open the Subnet Calculator.
- Enter 192.168.1.0/28 and verify you see the subnet analysis followed by the broadcast address concept note.
- Type back to return to the menu.
- Type 2 and press Enter, then enter 8.8.8.8 to test the Ping Monitor.
- Verify you see [UP] status with a response time and the ICMP concept note.
- Type back, then 4 to quit.
Seeing a NameError or ImportError?
A NameError: name 'combined_mode' is not defined means you placed combined_mode() inside another function instead of at the top level. Check the indentation.
An ImportError means Python cannot find your other files. Make sure subnet_calculator.py, ping_monitor.py, and network_toolkit.py are all in the same folder, and that your terminal is in that folder.
Help me debug my network toolkit errors.
✔️ Awesome, I've got everything!
Great. Double check you have saved network_toolkit.py.
ⓧ I'd like to double check the full code
Here is the complete network_toolkit.py file for comparison:
import ipaddress
from subnet_calculator import analyze_subnet, display_results as display_subnet
from ping_monitor import ping_multiple_hosts, display_results as display_ping
def show_menu():
"""Display the main menu."""
print("\n" + "=" * 40)
print(" NETWORK TOOLKIT")
print("=" * 40)
print(" [1] Subnet Calculator")
print(" [2] Ping Monitor")
print(" [3] Subnet + Ping (combined)")
print(" [4] Quit")
print("=" * 40)
def subnet_calculator_mode():
"""Run the subnet calculator interactively."""
print("\n--- Subnet Calculator ---")
print("Enter CIDR notation or 'back' to return to menu.\n")
while True:
user_input = input("Enter CIDR: ").strip()
if user_input.lower() in ("back", "b"):
break
if not user_input:
continue
result = analyze_subnet(user_input)
display_subnet(result)
print("\n [Concept] The broadcast address is used to send")
print(" a message to ALL hosts on this network simultaneously.")
print()
def ping_monitor_mode():
"""Run the ping monitor interactively."""
print("\n--- Ping Monitor ---")
print("Enter hosts (comma-separated) or 'back' to return to menu.")
print("Press Enter for defaults (8.8.8.8, 1.1.1.1).\n")
while True:
user_input = input("Hosts to ping: ").strip()
if user_input.lower() in ("back", "b"):
break
if not user_input:
hosts = ["8.8.8.8", "1.1.1.1"]
print(f" Using defaults: {', '.join(hosts)}")
else:
hosts = [h.strip() for h in user_input.split(",") if h.strip()]
if not hosts:
continue
print(" Pinging... please wait.")
results = ping_multiple_hosts(hosts)
display_ping(results)
print(" [Concept] Ping uses ICMP (Internet Control Message Protocol).")
print(" Some hosts block ICMP, so 'Down' may mean the firewall")
print(" is dropping packets, not that the host is offline.")
print()
def combined_mode():
"""Subnet calculation followed by pinging hosts in that subnet."""
print("\n--- Subnet + Ping ---")
print("Enter a CIDR notation to analyze and then ping key hosts.")
print("Only private networks (10.x, 172.16-31.x, 192.168.x) are allowed.\n")
user_input = input("Enter CIDR: ").strip()
if not user_input:
return
result = analyze_subnet(user_input)
if "error" in result:
display_subnet(result)
return
display_subnet(result)
# Safety check: only allow pinging private networks
try:
network = ipaddress.IPv4Network(user_input, strict=False)
except ValueError:
return
if not network.is_private:
print("\n [SAFETY] This is a public network. Ping sweep skipped.")
print(" Only scan networks you own or have permission to test.")
return
hosts = list(network.hosts())
if not hosts:
print("\n No usable hosts in this network to ping.")
return
# Pick a sample: first, last, and a middle host
sample_hosts = []
sample_hosts.append(str(hosts[0]))
if len(hosts) > 2:
sample_hosts.append(str(hosts[len(hosts) // 2]))
if len(hosts) > 1:
sample_hosts.append(str(hosts[-1]))
print(f"\n Pinging sample hosts: {', '.join(sample_hosts)}")
print(" (First, middle, and last usable host in the subnet)")
print(" Pinging... please wait.")
results = ping_multiple_hosts(sample_hosts)
display_ping(results)
def main():
"""Main entry point for the network toolkit."""
print("\nWelcome to the Network Toolkit!")
print("A hands-on tool for learning networking fundamentals.\n")
while True:
show_menu()
choice = input("\n Select an option (1-4): ").strip()
if choice == "1":
subnet_calculator_mode()
elif choice == "2":
ping_monitor_mode()
elif choice == "3":
combined_mode()
elif choice == "4":
print("\nGoodbye! Keep exploring networks.")
break
else:
print(" Invalid choice. Please enter 1, 2, 3, or 4.")
if __name__ == "__main__":
main()
Your network toolkit is fully operational. All three tools live under one unified menu, and each operation teaches a networking concept along the way. Next up, the Secret Mission challenges you to add a subnet splitter that divides networks into equal parts.
Secret mission
Add the Subnet Splitter
Your toolkit already calculates subnets and pings hosts. But what about splitting a large network into smaller, equal-sized subnets? This is the core skill tested in networking certifications. Build a subnet splitter that divides any network into N equal subnets and displays a full allocation table.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally using Python's standard library. There are no cloud resources, no paid services, and no ongoing costs.
Resources you used:
- Project folder containing subnet_calculator.py, ping_monitor.py, and network_toolkit.py (local files on your machine).
Decide whether to keep your project files, or delete them entirely.
Keep everything running
No action needed. Choose this if you want to keep experimenting with your network toolkit or extend it further.
- Your three Python files remain in your project folder, ready to run anytime.
- VS Code and Python stay installed on your machine for future projects.
Pause - I'll come back to this later
Since this project is local Python scripts only, there are no running processes or services to pause. Your files sit on disk and consume no resources until you run them.
- No action needed. Your project folder stays as-is until you return.
Delete - I don't want to use this again
Remove all project files from your machine. This deletes your subnet calculator, ping monitor, and network toolkit permanently.
- Delete your project folder using File Explorer or the terminal.
To delete via the terminal, navigate to the parent directory (e.g. your Desktop) and remove the project folder:
Windows
cd ~\Desktop
rmdir /s /q network-toolkit
macOS
cd ~/Desktop
rm -rf network-toolkit
Replace the folder name
If you named your project folder something different when you created it in Step 1, replace network-toolkit with whatever you named it.
That's a Wrap!
That's a Wrap!
Nice work! You just built a professional-quality network toolkit using nothing but Python's standard library.
You've learned how to:
- Built a subnet calculator that parses CIDR notation and extracts network address, broadcast address, subnet mask, and usable host range.
- Built a parallel ping monitor using ThreadPoolExecutor that tests reachability of multiple hosts simultaneously and reports response times.
- Combined both into a unified network toolkit with a menu-driven CLI that enforces safe scanning of private RFC 1918 networks only.
- Secret Mission: Built a subnet splitter that divides networks into equal subnets and displays an allocation table, a core network design skill.
Ready to quiz yourself?