Scapy ARP listener in Python

Scapy ARP listener in Python

This script listens for ARP request packets using scapy to learn the IP and Mac Address of LAN hosts.

A little background on the ARP protocol

ARP is the protocol that hosts use to discover the mac address of another LAN host. The initiating host asks “Who Has <IP Address>”, this request is transmitted as a broadcast ethernet packet to destination 00:00:00:00:00:00. Since the ARP request is sent as a broadcast, all hosts on the LAN receive and process this packet. Because of the nature of the protocol, no special port mirroring or tapping is required on the host that runs this script.

The host that has <IP Address> will reply back directly to the requester so we will not see that ARP reply packet unless our machine sent the initial ARP request.

This script is the foundation for creating a passive network discovery tool. We can collect and store the MAC Address, IP Address pairs for all hosts we hear communicating on the network.

DigitalOcean offers one-click deployment of popular applications such as WordPress, Django, MongoDB, Docker, and even preconfigured Kubernetes Clusters. Deploy your next app in seconds. Get $100 in cloud credits from DigitalOcean

Ad Notice I will receive a small commission that helps support this blog at no cost to you.

The Script

#!/usr/bin/env python3
"""scapy-arp-listener.py

Listen for arp packets using scapy to learn the IP and Mac Address of LAN hosts

Copyright (C) 2018 Jonathan Cutrer

License Dual MIT, 0BSD

"""

from __future__ import print_function
from scapy.all import *
import time

__version__ = "0.0.1"

def handle_arp_packet(packet):

    # Match ARP requests
    if packet[ARP].op == ARP.who_has:
        print('New ARP Request')
        print(packet.summary())
        #print(ls(packet))
        print(packet[Ether].src, "has IP", packet[ARP].psrc)

    # Match ARP replies
    if packet[ARP].op == ARP.is_at:
        print('New ARP Reply')
        print(packet.summary())
        #print(ls(packet))

    return

if __name__ == "__main__":
    sniff(filter="arp", prn=handle_arp_packet)
 
 

The script is also available as a github gist. https://gist.github.com/joncutrer/5d834e705f9ab4d2f9cc3fc6c4ed3c3d

Example Output

(venv) user@ubuntu:~/$ sudo ./venv/bin/python scapy-arp-listener.py
New ARP Request
Ether / ARP who has 172.16.##.48 says 172.16.##.34 / Padding
34:17:eb:##:##:## has IP 172.16.##.34
New ARP Request
Ether / ARP who has 172.16.##.48 says 172.16.##.34 / Padding
34:17:eb:##:##:## has IP 172.16.##.34
New ARP Request
Ether / ARP who has 172.16.##.152 says 172.16.##.78 / Padding
00:15:5d:##:##:## has IP 172.16.##.78
New ARP Request
Ether / ARP who has 172.16.##.74 says 172.16.##.25 / Padding
34:17:eb:##:##:## has IP 172.16.##.25
New ARP Request
Ether / ARP who has 172.16.##.86 says 172.16.##.25 / Padding
34:17:eb:##:##:## has IP 172.16.##.25
New ARP Request
Ether / ARP who has 172.16.##.48 says 172.16.##.34 / Padding
34:17:eb:##:##:## has IP 172.16.##.34

Environment

This script was developed and tested on a Ubuntu 18.10 host running python 3.6.7. Below, I have also included the requirements.txt of my virtual environment.

#requirements.txt
astroid==2.1.0
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
pkg-resources==0.0.0
pylint==2.2.2
scapy==2.4.0
six==1.12.0
typed-ast==1.1.1
wrapt==1.10.11

Troubleshooting

If you get the following error when running the script it’s because you need sudo/root privileges to the oses networking layers to be able to sniff ethernet frames. This is true for most scapy based applications.

(venv) user@ubuntu:~/$ ./venv/bin/python scapy-arp-listener.py
Traceback (most recent call last):
  File "scapy-arp-listener.py", line 36, in 
    sniff(filter="arp", prn=handle_arp_packet)
  File ".../venv/lib/python3.6/site-packages/scapy/sendrecv.py", line 731, in sniff
    *arg, **karg)] = iface
  File ".../venv/lib/python3.6/site-packages/scapy/arch/linux.py", line 567, in __init__
    self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type))
  File "/usr/lib/python3.6/socket.py", line 144, in __init__
    _socket.socket.__init__(self, family, type, proto, fileno)
PermissionError: [Errno 1] Operation not permitted

Amazon AWS too complex and expensive? You will love the simplicity of DigitalOcean. Deploy your next app in seconds. Get $100 in cloud credits from DigitalOcean

Ad Notice I will receive a small commission that helps support this blog at no cost to you.

License & Legal Disclaimer
The source code & script(s) contained in this article are dual licensed MIT & OBSD.

THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

If you find this script useful leave a comment below and also checkout my other Python Tutorials. I’ve also created a similar python script to analyze DHCP traffic on the LAN.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.