As Network Engineers, one of the most basic tasks for us is determining which hosts are currently active on a given network. One way to achieve this is through a ping sweep, which sends ICMP echo requests to every IP address within a given range and checks for responses. While there are other tools available that can perform this task, such as fping or bash scripts, Python offers several advantages that make it a compelling choice for this task.
In this blog post, we will explore how to perform a ping sweep using Python, and the benefits of using Python over other methods. We will also provide a step-by-step guide on how to write a simple Python script to perform a ping sweep of a given subnet, a list of subnets and explain how the script works.
Why Python?
Python is an excellent choice for performing a ping sweep due to several advantages over other tools. Python is cross-platform, meaning that it can run on Windows, Linux, and macOS. Additionally, Python offers greater flexibility and control over the ping sweep process than other tools, making it highly customizable.
Python is also easy to learn and use, making it an ideal choice for beginners. With its vast collection of libraries and modules, Python offers a wide range of options for customizing the ping sweep process, such as the ipaddress
module and ping3
library.
Overview
We will start by exploring how to perform a basic ping sweep of a single subnet using Python. We will write a Python script that sends ICMP echo requests to every IP address within a given range and records the responses to a file. We will then explain how the script works and how it can be customized to suit your specific needs.
Next, we will move on to more advanced topics, such as pinging multiple subnets in parallel using the concurrent.futures
module. We will show how to modify our original script to work with a list of subnets and demonstrate how to use the ThreadPoolExecutor
class to ping multiple subnets concurrently.
By the end of this blog post, you will have a clear understanding of how to perform a ping sweep using Python and how to customize the process to suit your needs. You will also have learned how to take advantage of Python's concurrency features to speed up the ping sweep process and make it more efficient.
Libraries
To perform the ping sweep in Python, we will be using the ping3
and ipaddress
libraries. The ping3
library is a third-party library that can be easily installed using the Python package manager, pip
. Once installed, you can import the ping
function from the ping3
module and use it to send ping requests.
The ipaddress
library is included in the Python standard library and provides a set of classes and functions for working with IP addresses and networks. It provides a simple way to generate a list of IP addresses within a given range using the ip_network()
function.
Getting Started
Before diving into the scrip, let's look at some simple examples on how to use these libraries.
ping3 Module
pip install ping3 # install ping
from ping3 import ping
a = ping('8.8.8.8')
b = ping ('25.5.6.7')
print(a)
print(b)
This Python code sends ICMP echo requests to two IP addresses (8.8.8.8
and 25.5.6.7
) using the ping
function from the ping3
library. It then prints the round-trip time for the successful ping request to 8.8.8.8
, and None
for the unsuccessful ping request to 25.5.6.7
as shown below.
#output
0.010473966598510742 # Returns delay in seconds
None # Returns none if the IP doesn't respond
ipaddress Module
import ipaddress
network = ipaddress.ip_network('192.168.1.0/29') # Creates subnet object
for ip in network:
print(ip) # Access each IP in that subnet
This Python code creates an ip_network
object that represents a subnet, then iterates over each IP address in the subnet using a for
loop and prints the value of each IP address to the console.
# output
192.168.1.0
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
192.168.1.5
192.168.1.6
192.168.1.7
Example 1 - Ping a Single Subnet
In this example, we will be looking at a Python script that can be used to ping each IP address within a given single subnet, while also timestamping each ICMP request and saving the output to a file.
import datetime
import ipaddress
from ping3 import ping
subnet = '192.168.102.0/29'
output_file = 'ping_results_single.txt'
def ping_sweep(subnet):
network = ipaddress.ip_network(subnet, strict=False)
with open(output_file, 'a') as f:
for ip in network.hosts():
ip_str = str(ip)
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
message = ""
try:
response_time = ping(ip_str, timeout=1)
if response_time is not None:
message = f"{timestamp} - {ip_str} is reachable"
else:
message = f"{timestamp} - {ip_str} is unreachable"
except Exception:
message = f"{timestamp} - Error pinging {ip_str}"
print(message)
f.write(message + '\n')
ping_sweep(subnet)
Once you run the script, this is the contents of the output file. You also get the same output on the terminal.
2023-04-14 10:00:37 - 192.168.102.1 is reachable
2023-04-14 10:00:37 - 192.168.102.2 is unreachable
2023-04-14 10:00:38 - 192.168.102.3 is unreachable
2023-04-14 10:00:40 - 192.168.102.4 is reachable
2023-04-14 10:00:41 - 192.168.102.5 is reachable
2023-04-14 10:00:42 - 192.168.102.6 is unreachable
Example 2 - Ping Multiple Subnets
In this second example, we will be looking at a Python script that can be used to ping a list of subnets.
import datetime
import ipaddress
from ping3 import ping
import concurrent.futures
import threading
subnets = ['192.168.102.0/29', '192.168.107.0/29', '192.168.108.0/29'] # Add your subnets here
output_file = 'ping_results.txt'
output_lock = threading.Lock()
def ping_sweep(subnet):
network = ipaddress.ip_network(subnet, strict=False)
results = []
for ip in network.hosts():
ip_str = str(ip)
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
try:
response_time = ping(ip_str, timeout=1)
if response_time is not None:
message = f"{timestamp} - {ip_str} is reachable"
else:
message = f"{timestamp} - {ip_str} is unreachable"
except Exception:
message = f"{timestamp} - Error pinging {ip_str}"
results.append(message)
with output_lock:
print(f"Ping results for subnet {subnet}:")
with open(output_file, 'a') as f:
f.write(f"Ping results for subnet {subnet}:\n")
for msg in results:
print(msg)
f.write(msg + '\n')
f.write('\n')
print()
with concurrent.futures.ThreadPoolExecutor() as executor:
executor.map(ping_sweep, subnets)
Here is an explanation of the code line by line, excluding the import
statements and the initial variable declarations.
output_lock = threading.Lock()
: Creates aLock
object from thethreading
module to be used for ensuring thread-safe access to shared resources, like the output file.def ping_sweep(subnet):
: Defines a function namedping_sweep
that takes a subnet as its input argument.network = ipaddress.ip_network(subnet, strict=False)
: Creates anip_network
object representing the input subnet, allowing for the manipulation of IP addresses within the subnet.results = []
: Initializes an empty list to store the ping results for the hosts within the subnet.for ip in network.hosts():
: Iterates through the host IP addresses in the subnet.ip_str = str(ip)
: Converts theipaddress.IPv4Address
object to a string representation.timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
: Creates a timestamp string for the current time, formatted as 'YYYY-MM-DD HH:MM:SS'.try-except
block: Attempts to ping the current IP address, and constructs a message string based on the result:- If the ping is successful (i.e.,
response_time
is notNone
), the message will indicate that the IP address is reachable. - If the ping is unsuccessful, the message will indicate that the IP address is unreachable.
- If there's an exception during the ping process, the message will indicate that there was an error pinging the IP address.
results.append(message)
: Appends the message to theresults
list.with output_lock:
: Acquires theoutput_lock
to ensure thread-safe access to the shared resource (i.e., the output file).print(f"Ping results for subnet {subnet}:")
: Prints a header line indicating the start of the ping results for the current subnet.with open(output_file, 'a') as f:
: Opens the output file in append mode, so that new results can be added to the existing content.- For loop: Iterates through the messages in the
results
list and performs the following actions for each message: - Prints the message to the console.
- Writes the message to the output file.
f.write('\n')
: Adds an empty line to the output file to separate the results of different subnets.print()
: Prints an empty line to the console to separate the results of different subnets.with concurrent.futures.ThreadPoolExecutor() as executor:
: Creates aThreadPoolExecutor
that manages a pool of worker threads, allowing for the concurrent execution of multiple instances of theping_sweep
function.executor.map(ping_sweep, subnets)
: Calls theping_sweep
function with each subnet in thesubnets
list, distributing the work across the threads in the thread pool. The function runs concurrently for each subnet, and themap
function blocks until all the work is complete.
Here are the contents of the output file
Ping results for subnet 192.168.107.0/29:
2023-04-14 09:44:54 - 192.168.107.1 is reachable
2023-04-14 09:44:54 - 192.168.107.2 is unreachable
2023-04-14 09:44:55 - 192.168.107.3 is unreachable
2023-04-14 09:44:56 - 192.168.107.4 is reachable
2023-04-14 09:44:56 - 192.168.107.5 is unreachable
2023-04-14 09:44:57 - 192.168.107.6 is unreachable
Ping results for subnet 192.168.108.0/29:
2023-04-14 09:44:54 - 192.168.108.1 is reachable
2023-04-14 09:44:54 - 192.168.108.2 is unreachable
2023-04-14 09:44:55 - 192.168.108.3 is reachable
2023-04-14 09:44:56 - 192.168.108.4 is unreachable
2023-04-14 09:44:57 - 192.168.108.5 is unreachable
2023-04-14 09:44:58 - 192.168.108.6 is unreachable
Ping results for subnet 192.168.102.0/29:
2023-04-14 09:44:54 - 192.168.102.1 is reachable
2023-04-14 09:44:54 - 192.168.102.2 is unreachable
2023-04-14 09:44:55 - 192.168.102.3 is unreachable
2023-04-14 09:44:56 - 192.168.102.4 is reachable
2023-04-14 09:44:57 - 192.168.102.5 is reachable
2023-04-14 09:44:58 - 192.168.102.6 is reachable
Conclusion
In conclusion, the first script performs a ping sweep on a single subnet and outputs the results to a file, while the second script extends this functionality by taking a list of subnets and leveraging the concurrent.futures
module to run ping sweeps on multiple subnets in parallel, providing a more efficient solution for scanning larger network ranges.