How to Ping Sweep Your Network with Python - A Beginner's Guide

How to Ping Sweep Your Network with Python - A Beginner's Guide
In: Python NetDevOps

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.

  1. output_lock = threading.Lock(): Creates a Lock object from the threading module to be used for ensuring thread-safe access to shared resources, like the output file.
  2. def ping_sweep(subnet):: Defines a function named ping_sweep that takes a subnet as its input argument.
  3. network = ipaddress.ip_network(subnet, strict=False): Creates an ip_network object representing the input subnet, allowing for the manipulation of IP addresses within the subnet.
  4. results = []: Initializes an empty list to store the ping results for the hosts within the subnet.
  5. for ip in network.hosts():: Iterates through the host IP addresses in the subnet.
  6. ip_str = str(ip): Converts the ipaddress.IPv4Address object to a string representation.
  7. 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'.
  8. try-except block: Attempts to ping the current IP address, and constructs a message string based on the result:
  9. If the ping is successful (i.e., response_time is not None), the message will indicate that the IP address is reachable.
  10. If the ping is unsuccessful, the message will indicate that the IP address is unreachable.
  11. If there's an exception during the ping process, the message will indicate that there was an error pinging the IP address.
  12. results.append(message): Appends the message to the results list.
  13. with output_lock:: Acquires the output_lock to ensure thread-safe access to the shared resource (i.e., the output file).
  14. print(f"Ping results for subnet {subnet}:"): Prints a header line indicating the start of the ping results for the current subnet.
  15. 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.
  16. For loop: Iterates through the messages in the results list and performs the following actions for each message:
  17. Prints the message to the console.
  18. Writes the message to the output file.
  19. f.write('\n'): Adds an empty line to the output file to separate the results of different subnets.
  20. print(): Prints an empty line to the console to separate the results of different subnets.
  21. with concurrent.futures.ThreadPoolExecutor() as executor:: Creates a ThreadPoolExecutor that manages a pool of worker threads, allowing for the concurrent execution of multiple instances of the ping_sweep function.
  22. executor.map(ping_sweep, subnets): Calls the ping_sweep function with each subnet in the subnets list, distributing the work across the threads in the thread pool. The function runs concurrently for each subnet, and the map 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.

Table of Contents
Written by
Suresh Vina
Tech enthusiast sharing Networking, Cloud & Automation insights. Join me in a welcoming space to learn & grow with simplicity and practicality.
Comments
More from Packetswitch
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Packetswitch.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.