Find Wi-Fi Connected Devices with Python

This article provides a step by step approach to building a Python script for identifying devices connected to a Wi-Fi network. By leveraging Address Resolution Protocol (ARP) scanning and MAC address vendor lookup tools, the solution automates device discovery and enhances network visibility. Below, we explore the theory and steps, and optimization hacks for building a robust network monitoring tool.


Bookmark This Article

Your browser doesn't support automatic bookmarking. You can:

  1. Press Ctrl+D (or Command+D on Mac) to bookmark this page
  2. Or drag this link to your bookmarks bar:
Bookmark This

Clicking this bookmarklet when on any page of our site will bookmark the current page.

Technical Foundations of Network Device Discovery

ARP Protocol and Network Scanning

The Address Resolution Protocol (ARP) resolves IP addresses to MAC addresses within a local network. When a device joins a Wi-Fi network, it broadcasts ARP requests to establish communication with other devices. By analyzing ARP traffic or actively sending ARP probes, we can compile a list of connected devices^1.

Python’s scapy library simplifies ARP packet crafting and response parsing. For example, scapy.ARP(pdst=ip_range) generates ARP requests for a specified IP range, while scapy.srp() sends and receives packets at the data link layer^2.

Automating Interface and Subnet Detection

Manually specifying the network interface and IP range is error-prone. Instead, the netifaces library programmatically retrieves the default gateway’s IP and subnet mask. The netifaces.gateways() function identifies the active interface, while netifaces.ifaddresses() extracts its IP configuration^4.

import netifaces

def get_default_gateway():
    gateways = netifaces.gateways()
    default = gateways.get('default', {})
    return default.get(netifaces.AF_INET, (None, None))[^1]

This code snippet fetches the default interface and its IP, enabling dynamic adaptation to different network environments^5.


Step-by-Step Implementation

Step 1: Install Dependencies

Install required libraries using pip:

pip install scapy netifaces mac-vendor-lookup  
  • scapy: Craft and send ARP packets^2.
  • netifaces: Automate interface and IP detection^4.
  • mac-vendor-lookup: Resolve MAC addresses to vendor names^7.

Step 2: Perform ARP Scan

The arp_scan() function sends ARP requests to all IPs in the subnet and parses responses:

from scapy.all import ARP, Ether, srp

def arp_scan(ip_range, interface):
    arp_request = ARP(pdst=ip_range)
    broadcast = Ether(dst="ff:ff:ff:ff:ff:ff")
    packet = broadcast/arp_request
    answered, _ = srp(packet, iface=interface, timeout=2, verbose=False)

    devices = []
    for sent, received in answered:
        devices.append({'ip': received.psrc, 'mac': received.hwsrc})
    return devices

This function returns a list of dictionaries containing IP and MAC addresses^1.

Step 3: Resolve MAC Vendors

The mac-vendor-lookup library queries a local copy of the IEEE OUI database to identify device manufacturers:

from mac_vendor_lookup import MacLookup

def get_vendor(mac):
    try:
        return MacLookup().lookup(mac)
    except:
        return "Unknown"

For accuracy, update the vendor list periodically using MacLookup().update_vendors()^7.


Integrating Components into a Complete Script

Dynamic IP Range Calculation

Convert the gateway IP and subnet mask into a CIDR notation (e.g., 192.168.1.0/24):

import ipaddress

def calculate_subnet(gateway_ip, netmask):
    network = ipaddress.IPv4Network(f"{gateway_ip}/{netmask}", strict=False)
    return str(network)

Full Script

Combine all components into a single script:

import netifaces
import ipaddress
from scapy.all import ARP, Ether, srp
from mac_vendor_lookup import MacLookup

def get_network_info():
    gateways = netifaces.gateways()
    default_gateway = gateways['default'][netifaces.AF_INET]
    interface = default_gateway[^1]
    gateway_ip = default_gateway[^0]

    addresses = netifaces.ifaddresses(interface)[netifaces.AF_INET][^0]
    netmask = addresses['netmask']
    subnet = ipaddress.IPv4Network(f"{gateway_ip}/{netmask}", strict=False)
    return interface, str(subnet)

def main():
    interface, subnet = get_network_info()
    print(f"Scanning {subnet} on interface {interface}...")

    devices = arp_scan(subnet, interface)
    print("\nConnected Devices:")
    print("IP Address\t\tMAC Address\t\tVendor")
    for device in devices:
        vendor = get_vendor(device['mac'])
        print(f"{device['ip']}\t\t{device['mac']}\t\t{vendor}")

if __name__ == "__main__":
    main()

Optimization and Advanced Features

Asynchronous Scanning

To improve performance on large networks, implement asynchronous scanning using scapy’s AsyncSniffer:

from scapy.all import AsyncSniffer

async def async_arp_scan(ip_range, interface):
    sniffer = AsyncSniffer()
    sniffer.start()
    # Send ARP requests and analyze responses asynchronously

Scheduled Scans

Use schedule library to run scans periodically:

import schedule
import time

schedule.every(5).minutes.do(main)
while True:
    schedule.run_pending()
    time.sleep(1)

Conclusion and Recommendations

This script provides a foundational tool for network monitoring. For enterprise environments, consider integrating with SNMP for deeper insights or adding alerts for unauthorized devices. Always ensure compliance with local network policies, as ARP scanning may trigger security alerts. Future enhancements could include GUI dashboards or integration with intrusion detection systems.

By combining low-level packet manipulation with vendor databases, Python enables flexible and powerful network analysis tailored to specific operational needs.