What if you could see everything happening on your network? Every website visited, every connection made, every piece of data zipping by? To most people, a computer network is a black box. You plug in a cable or connect to Wi-Fi, and magic happens. But for developers, network engineers, and cybersecurity professionals, that “magic” is a complex, fascinating, and crucially, observable flow of data.
Welcome to the world of network sniffing.
In this ultimate, guide, we are going to build a powerful network sniffer from the ground up. We’re not just going to copy-paste a 10-line script. We are going to embark on a journey. We will start with the absolute fundamentals—”What is a network packet?”—and go all the way to building a complete, command-line-driven analysis tool.
Our tools of choice? Python, the world’s most popular and readable programming language, and Scapy, the “Swiss Army knife” of packet manipulation. With these two, you can build tools that rival expensive, commercial-grade software.
This guide is for everyone. If you’re a computer science student, this is the project that will make networking theory click. If you’re a budding cybersecurity analyst, this is a foundational skill. If you’re a network admin, this will give you a new weapon for your debugging arsenal.
Here’s what we will cover:
- Networking Fundamentals: A deep dive into the OSI model, TCP/IP, packets, and frames.
- Setting Up Your Lab: How to properly install Python, virtual environments, and Scapy on Windows, macOS, and Linux.
- Your First Sniffer: Writing your first few lines of Scapy to capture live traffic.
- Packet Dissection: Tearing packets apart to extract IP addresses, ports, and raw data.
- Filtering the Noise: Using Berkeley Packet Filters (BPF) to find the exact traffic you care about.
- Advanced Analysis: We’ll build specific sniffers for HTTP (unencrypted web traffic) and DNS (domain lookups).
- Saving & Reading: How to save your captures to PCAP files and analyze them offline (just like Wireshark!).
- Beyond Sniffing: We’ll explore Scapy’s real power by crafting and sending our own packets to scan a network.
- Building a Complete Tool: We’ll use
argparseto create a professional command-line tool. - The Ethical Code: The most important chapter. How to use this power responsibly, legally, and ethically.
By the end of this post, you will not only have a working network sniffer, but you will also have a profound, practical understanding of how the internet actually works.
A CRUCIAL ETHICAL WARNING (Read This First!)
This tool is incredibly powerful. The same techniques used to debug your own application can be used to spy on others. Using a network sniffer on any network you do not own or have explicit, written permission to monitor is illegal in most countries. It can be a serious crime, equivalent to wiretapping.
This tutorial is for educational and research purposes ONLY. You are to use this code exclusively on:
- Your own personal, private home network.
- Networks you have been paid and authorized (in writing!) to test as a security professional.
- Virtual networks within your own computer (e.g., in a VM).
We are “white hats.” We build and learn to protect and understand, not to harm or steal. Do not ever run this tool on a public Wi-Fi network (like a café), at your school, or at your workplace without authorization.
Now, with that clear, let’s begin.

The Absolute Fundamentals (What We’re Actually Sniffing)
Before we write a single line of code, we must understand what we’re looking for. If you just jump into the code, a “packet” will be a meaningless jumble of data. This chapter builds the foundation.
What is a Network? (The 1-Minute Version)
A network is simply two or more computers connected so they can share resources and communicate. Your home network (a LAN, or Local Area Network) connects your phone, laptop, and smart TV to your router. That router, in turn, connects to your Internet Service Provider’s (ISP) massive network, which connects to other massive networks, forming the global WAN, or Wide Area Network—the Internet.
To send a message from your laptop to a server (like google.com), your data goes on a journey: Laptop -> Router -> ISP -> … -> Google’s Server.
What is a Network Packet?
Data isn’t sent in one giant, continuous stream. That would be inefficient. If you were downloading a 5GB file, nobody else in your house could check their email.
Instead, that 5GB file is broken down into thousands of tiny, manageable pieces called packets.
Think of it like sending a 1,000-page book through the mail. You wouldn’t put it in one giant, unliftable box. You’d send it as 100 separate 10-page envelopes. Each envelope would have:
- A “To” address (Destination IP).
- A “From” address (Source IP).
- A sequence number (“This is envelope 5 of 100”).
- The actual payload (10 pages of the book).
This way, if one envelope gets lost, the receiver just has to ask for “envelope 5” again, not the entire book. This is the core principle of packet-switched networks.
Our sniffer is a tool that intercepts and reads these “envelopes” as they fly by.
The OSI Model: The Blueprint of the Internet
This is the most important networking theory you will ever learn. The “Open Systems Interconnection” (OSI) model is a 7-layer framework that standardizes how network communication happens. We only really care about 4-5 of them for sniffing.
When your computer sends data, it’s like building a Russian nesting doll. The data is wrapped in layer after layer of information. Our sniffer’s job is to unwrap this doll.

Let’s look at the layers that matter to us, from top to bottom (as data is created):
- Layer 7 (Application): The “human-readable” layer. This is the protocol your app speaks. When you browse a website, your browser speaks HTTP (or HTTPS). When you look up a domain, it’s DNS.
- Layer 4 (Transport): This layer manages the connection and ensures data integrity. The two main protocols are:
- TCP (Transmission Control Protocol): The “reliable” one. It establishes a formal connection (a “three-way handshake”), numbers every packet, and requires the receiver to send an acknowledgment (
ACK). If a packet is lost, TCP re-sends it. This is used for web browsing, email, and file downloads—things that must be 100% complete. - UDP (User Datagram Protocol): The “fire and forget” one. It’s fast, simple, and has low overhead. It just sends datagrams without checking if they arrived. This is used for video streaming, online gaming, and DNS—things where speed is more important than perfect accuracy.
- TCP (Transmission Control Protocol): The “reliable” one. It establishes a formal connection (a “three-way handshake”), numbers every packet, and requires the receiver to send an acknowledgment (
- Layer 3 (Network): This is the “inter-network” or “routing” layer. Its job is to get packets from one network to another.
- IP (Internet Protocol): This is the king. This layer adds the Source IP address and Destination IP address. This is like the main “To” and “From” addresses on your envelope. It handles routing across the internet.
- Layer 2 (Data Link): This is the “local network” layer. Its job is to get a packet from one device to another on the same local network.
- Ethernet: This is the protocol that adds the Source MAC address and Destination MAC address. A MAC address is a unique hardware ID for your network card. This is like the mail carrier’s local delivery instructions (e.g., “move from the sorting truck to this specific mailbox”).
- Layer 1 (Physical): The actual wires, fiber optics, or radio waves. We can’t sniff this with Scapy.
When our sniffer captures a “packet,” it’s technically an Ethernet frame (Layer 2) which contains an IP packet (Layer 3) which contains a TCP segment (Layer 4) which contains HTTP data (Layer 7). Scapy is brilliant because it lets us access each “doll” in the stack: packet[IP].src or packet[TCP].dport.
What is Promiscuous Mode?
By default, your computer’s network card (NIC) is polite. It looks at the destination MAC address of every frame on the local network and asks, “Is this for me?” If the answer is no, it discards the frame instantly. It never even reaches your operating system.
Promiscuous Mode is a special setting that tells your NIC: “Stop being polite. I want to see everything.” With this mode on, the NIC grabs every single frame it sees on the wire, regardless of the destination, and passes it up to the OS.
This is what allows a sniffer to work. It’s also why sniffers require administrator or root privileges—you are changing a fundamental, low-level setting on a piece of hardware.
Our Tools: Python and Scapy
Why Python? It’s simple, readable, and has a massive ecosystem. For a task like this, which involves a lot of string, byte, and logic manipulation, Python is a joy to use compared to C or Java.
What is Scapy? Scapy is a masterpiece. It’s a pure-Python library that enables you to sniff, dissect, forge, and send network packets.
- Dissect: It can take a raw stream of bytes from the network and automatically decode it into the OSI model:
Ethernet / IP / TCP / Raw. - Forge: You can build a packet from scratch just by stacking layers:
pkt = IP(dst="google.com") / ICMP(). - Send: It can send these forged packets onto the network.
Scapy vs. Wireshark vs. tcpdump
You’ve probably heard of Wireshark. It’s the world’s most popular network analyzer, and it has a fantastic GUI. tcpdump is the command-line equivalent, fast and powerful, found on almost every server.
So why use Scapy?
- Wireshark is for analysis. You capture, then you use its powerful GUI to click, filter, and explore.
- tcpdump is for capture. You capture on a remote server and save to a file, which you later analyze in Wireshark.
- Scapy is for automation and interaction. It’s a programming tool. You can’t just sniff with Scapy; you can react.
With Scapy, you can write a script that says: “IF I sniff a DNS query for a malicious domain, THEN immediately craft and send a spoofed “Access Denied” packet to the user and log the user’s IP to a database.”
That’s power. Wireshark can’t do that. Scapy bridges the gap between sniffing and acting.

Setting Up Your Laboratory (The Right Way)
Let’s get our hands dirty. Like any science experiment, a clean, isolated lab is critical.
The Cardinal Rule: Use a Virtual Environment!
I cannot stress this enough. Do not pip install packages into your system’s global Python. This is a recipe for disaster, as you’ll eventually have two projects that need different versions of the same library.
A Python virtual environment is a self-contained directory that holds a specific version of Python and all the libraries needed for just one project.
- Check for Python: Open your terminal (Terminal on macOS/Linux, PowerShell or CMD on Windows) and type:
python3 --version # If that fails, try: python --versionIf you don’t have Python 3.8 or newer, go topython.organd install it. - Create Your Project Folder:
mkdir python-sniffer cd python-sniffer - Create the Virtual Environment:
# On macOS/Linux python3 -m venv sniffer-env # On Windows python -m venv sniffer-envYou’ll see a new folder namedsniffer-env. This is your isolated lab. - Activate the Environment: This is the most important step! You must do this every time you work on this project.
# On macOS/Linux (bash/zsh) source sniffer-env/bin/activate # On Windows (PowerShell) # You may need to run: Set-ExecutionPolicy Unrestricted -Scope Process .\sniffer-env\Scripts\Activate.ps1 # On Windows (CMD) .\sniffer-env\Scripts\activate.batYou’ll know it worked because your terminal prompt will change to show(sniffer-env)at the beginning.

Installing Scapy
Now that our lab is active, we can install our tools. Inside your activated (sniffer-env) terminal, run:
pip install scapy
That’s it! pip will fetch Scapy and its dependencies and install them inside the sniffer-env folder, leaving your system’s Python untouched.
Platform-Specific Headaches (And How to Fix Them)
This is where 90% of beginners get stuck. Sniffing is a low-level operation, and different operating systems handle it differently.
On Windows: Scapy on its own cannot sniff on Windows. It needs a driver to access the network card.
- The Solution: You must install Npcap.
- Go to the official Npcap website (search for “Npcap download”).
- Download the latest installer.
- CRITICAL: During installation, make sure you check the box that says “Install Npcap in WinPcap API-compatible Mode”. Scapy (and many other tools like the old Wireshark) traditionally used WinPcap. Checking this box ensures Scapy can find and use Npcap.
- After installing, you may need to restart your terminal or even your computer.
On macOS: macOS is generally well-supported and should work out of the box after pip install scapy.
On Linux (e.g., Ubuntu, Debian, Fedora): Linux is Scapy’s native home. It will almost certainly work. However, you might be missing some kernel headers or libraries. If pip install scapy fails with a weird error, you might need to install Python’s development files:
sudo apt-get update
sudo apt-get install python3-dev
# Then try pip install scapy again
The most common “problem” on Linux and macOS isn’t an error, it’s a permission issue…
The Permission Problem: “Sudo” is Your Friend
Let’s try to run the Scapy interactive shell. In your (sniffer-env) prompt, type:
scapy
You might get a command not found error. That’s fine. Try:
python -m scapy
You’ll probably see a big red warning: WARNING: No routes found. Can't reach any host.... This means Scapy can’t access the network properly.
Now exit (exit()) and try this:
# On macOS/Linux
sudo python -m scapy
# On Windows
# You must run your PowerShell or CMD as "Administrator"
# Then activate your venv and run:
python -m scapy
Remember promiscuous mode? It requires root/admin privileges. All your Scapy scripts that sniff or send packets must be run as root/administrator.
Inside the Scapy shell (it has a >>> prompt), let’s test it.
>>> conf.iface
This will show you the network interface Scapy has chosen by default. Now, let’s list all the protocols Scapy knows:
>>> lsc()
You’ll see a massive list (ARP, DNS, IP, TCP, etc.). Finally, let’s see the “blueprint” for an IP packet:
>>> ls(IP)
version : BitField = (4)
ihl : BitField = (None)
tos : XByteField = (0)
len : ShortField = (None)
id : ShortField = (1)
flags : FlagsField = (0)
frag : BitField = (0)
ttl : ByteField = (64)
proto : ByteEnumField = (0)
chksum : XShortField = (None)
src : SourceIPField = (None)
dst : DestIPField = (None)
options : PacketListField = ([])
This is amazing! Scapy is literally showing you every field inside an IP packet header and its default value. You can see src (source IP), dst (destination IP), and ttl (Time to Live).
We’re ready. Let’s build.
The “Hello, World!” of Sniffing
Let’s write our first real sniffer script.
Close the Scapy shell (exit()). In your python-sniffer folder, create a new file named sniffer_v1.py.
Your First Sniffer (The 3-Liner)
Open sniffer_v1.py in your favorite code editor and type this:
from scapy.all import *
print("Starting sniffer...")
# Sniff 5 packets and then stop
packets = sniff(count=5)
print("Sniffing complete. Packets captured:")
packets.summary()
Save the file. Now, go to your terminal (with the (sniffer-env) activated) and run it. Remember to use sudo!
# macOS/Linux
sudo python3 sniffer_v1.py
# Windows (in an Admin terminal)
python sniffer_v1.py
You’ll see “Starting sniffer…”. Then, it will pause. Your computer is now listening. To generate some traffic, open a new browser tab and visit any website (e.g., google.com). After a few seconds, the script will finish and print something like this:

This output is Scapy’s summary() view. It’s showing you the top-level layers of the 5 packets it captured. You can clearly see Ethernet, IP, and TCP layers, as well as source and destination IPs and ports.
Congratulations. You are now officially a packet sniffer.
Breaking Down sniff()
The sniff() function is the heart of our program. It has many arguments, but these are the most important:
count=N: Stop sniffing afterNpackets. If 0 (the default), it sniffs forever.iface="eth0": Specify which network interface to listen on. This is vital. If you’re on Wi-Fi, it might bewlan0oren0. If you’re plugged in, it’s probablyeth0. You can find your interfaces withifconfig(Linux/macOS) oripconfig(Windows).store=1: By default, Scapy stores all sniffed packets in memory. If you setstore=0, Scapy won’t save them. This is essential for long-running sniffers, or you’ll run out of RAM.prn=FUNCTION: This is the most important argument. It stands for “process.” You can pass it a Python function, and Scapy will run that function on every single packet it sniffs, in real-time.
Real-Time Processing with prn (The Right Way to Sniff)
Storing packets in a list and processing them later is fine for small tests. A real sniffer analyzes packets as they arrive. This is where the prn callback comes in.
Let’s create sniffer_v2.py:
from scapy.all import *
# This is our callback function
def process_packet(packet):
"""
This function will be called for each packet sniffed.
"""
print(packet.summary())
print("Starting real-time sniffer... Press Ctrl+C to stop.")
# Start sniffing.
# store=0: Don't store packets in memory (we're processing in real-time)
# prn=process_packet: Call our function for each packet
# count=0: Sniff forever (until we press Ctrl+C)
sniff(store=0, prn=process_packet, count=0)
Now, run this (with sudo!):
sudo python3 sniffer_v2.py
You’ll see “Starting real-time sniffer…”. Now, just browse the web. You will see a live, scrolling feed of all the traffic your computer is sending and receiving. Press Ctrl+C to stop it.
This is the fundamental pattern for 99% of all network tools. You have a sniff() loop and a process_packet() callback that does all the work.
Diving Inside the Packet
The summary() view is nice, but we want the raw data. Let’s modify our process_packet function.
Replace print(packet.summary()) with packet.show(). This is a super useful Scapy method.
def process_packet(packet):
"""
This function will be called for each packet sniffed.
"""
# .show() gives a detailed, multi-line breakdown
print("===== New Packet =====")
packet.show()
print("="*20)
Run your sniffer_v2.py again. Now, the output will be far more detailed:

This is our Russian nesting doll, unwrapped! You can see every single field, from the Ethernet src MAC address to the IP src address, to the TCP sport (source port) and dport (destination port).
Accessing Layers and Fields (The Core Skill)
The .show() method is for humans. For our program, we access layers using [] and fields using ..
But wait! Not all packets are TCP/IP. Some are ARP. Some are UDP. If we try to access packet[TCP].sport on an ARP packet, our script will crash.
The safe way to dissect a packet is to first ask it what it has, using the .haslayer() method.
Let’s build our “smarter sniffer.” Create sniffer_v3.py.
from scapy.all import *
def process_packet(packet):
"""
This function will be called for each packet sniffed.
"""
# Check if the packet has an IP layer
if packet.haslayer(IP):
src_ip = packet[IP].src
dst_ip = packet[IP].dst
# Check if it's TCP
if packet.haslayer(TCP):
src_port = packet[TCP].sport
dst_port = packet[TCP].dport
flags = packet[TCP].flags
print(f"[TCP Packet] {src_ip}:{src_port} -> {dst_ip}:{dst_port} [Flags: {flags}]")
# Check if it's UDP
elif packet.haslayer(UDP):
src_port = packet[UDP].sport
dst_port = packet[UDP].dport
print(f"[UDP Packet] {src_ip}:{src_port} -> {dst_ip}:{dst_port}")
# Check if it's ICMP (like a 'ping')
elif packet.haslayer(ICMP):
print(f"[ICMP Packet] {src_ip} -> {dst_ip}")
# It's some other IP packet
else:
print(f"[IP Packet] {src_ip} -> {dst_ip}")
# --- Main sniffing loop ---
print("Starting smart sniffer... Press Ctrl+C to stop.")
# We can specify an interface here.
# On Linux, it might be 'eth0' or 'wlan0'. On macOS, 'en0'.
# Use 'ifconfig' or 'ipconfig' to find yours.
# If you get "No such device" errors, change this.
# conf.iface = "your-interface-name" # e.g., "eth0"
sniff(store=0, prn=process_packet)
Run this (with sudo). You now have a clean, structured log of your network’s most common traffic! This is a huge step up. You’ll see your [TCP Packet] lines for web browsing, [UDP Packet] lines for DNS lookups (usually on port 53), and [ICMP Packet] lines if you try to ping google.com in another terminal.
This if packet.haslayer(...) -> var = packet[Layer].field pattern is the fundamental building block of all Scapy analysis.
Filtering the Noise (Finding the Needle in the Haystack)
If you run sniffer_v3.py for even 30 seconds, you’ll be overwhelmed. Your computer is constantly chattering—ARP broadcasts, mDNS (Bonjour), SSDP, and dozens of other “background” protocols.
We don’t want to drink from the firehose. We want a glass of water.
We could add more if statements inside our process_packet function (e.g., if packet[TCP].dport == 80:). This is called Python-side filtering. It’s bad.
Why? Because our Python script still has to process 100% of the packets, just to throw 99% of them away. It’s slow and inefficient.
The correct way is to filter at the kernel level, before the packets even reach our Python script. We do this with Berkeley Packet Filters (BPF).
BPF: The “Query Language” for Packets
BPF is a simple but powerful string-based syntax that the sniff() function understands. You pass it a BPF string with the filter argument.
The filtering happens in your operating system’s kernel, which is blazing fast. If a packet doesn’t match the filter, it’s dropped instantly and your Python code never even knows it existed.
Let’s look at the BPF “Cheat Sheet.”
BPF Syntax Examples:
"tcp": Only TCP packets."udp": Only UDP packets."port 80": Any traffic (TCP or UDP) to or from port 80 (HTTP)."tcp and port 80": Only TCP traffic to or from port 80."host 192.168.1.1": Any traffic to or from this IP."src host 192.168.1.1": Only traffic from this IP."dst host 8.8.8.8": Only traffic to this IP (Google’s DNS)."host 8.8.8.8 and udp and port 53": DNS queries/responses to Google."not arp": A very common one. “Give me everything except ARP.”"(tcp port 80 or tcp port 443) and not host 192.168.1.1": Web traffic (HTTP/HTTPS) that is not from my own machine.
The HTTP-Only Sniffer
Let’s use our new power. We only want to see unencrypted web traffic. Create sniffer_v4_http.py.
from scapy.all import *
def process_packet(packet):
"""
This function will be called for each packet sniffed.
We already know it's a TCP packet on port 80,
so we can safely access its layers.
"""
src_ip = packet[IP].src
dst_ip = packet[IP].dst
src_port = packet[TCP].sport
dst_port = packet[TCP].dport
print(f"[HTTP?] {src_ip}:{src_port} -> {dst_ip}:{dst_port}")
# We'll look at the payload in the next chapter!
if packet.haslayer(Raw):
print(" [+] Payload detected!")
# --- Main sniffing loop ---
BPF_FILTER = "tcp and port 80"
print(f"Starting HTTP sniffer (filter: '{BPF_FILTER}')... Press Ctrl+C to stop.")
sniff(store=0, prn=process_packet, filter=BPF_FILTER)
Now, run this (with sudo). At first, you’ll see… nothing. Your terminal will be silent. Why? Most web traffic today is HTTPS (port 443), which is encrypted. To test our sniffer, you must find a website that still uses http://. A good one for testing is http://httpforever.com/.
Visit that site in your browser. As soon as you do, your terminal will explode with packets matching our filter. You’ll see your IP connecting to the website’s IP on port 80.

This is the power of BCF. You’ve told the kernel, “Don’t bother me unless it’s TCP on port 80.” This makes your sniffer incredibly efficient.
Advanced Packet Dissection (Looking at the Data)
So far, we’ve only looked at the “envelopes” (the headers: IP, TCP, etc.). But what about the “letter” inside? This is the payload, and Scapy gives it to us in the Raw layer.
Case Study 1: Sniffing HTTP GET Requests
Let’s upgrade our sniffer_v4_http.py. We’re not just going to see HTTP traffic; we’re going to see what’s being requested.
The data inside the Raw layer is just raw bytes. We need to .decode() it into a string we can read. We’ll use .decode('utf-8', errors='ignore') to prevent crashes on weird characters.
Create sniffer_v5_http_payload.py:
from scapy.all import *
import re
def process_packet(packet):
"""
This function will be called for each packet sniffed.
"""
# Check if it has a Raw payload
if packet.haslayer(Raw):
try:
# Decode payload, ignoring errors
payload = packet[Raw].load.decode('utf-8', errors='ignore')
# HTTP traffic is plain text. We can look for common keywords.
# We are looking for the 'GET' request line
if payload.startswith("GET"):
src_ip = packet[IP].src
dst_ip = packet[IP].dst
print(f"\n[+] HTTP GET Request from {src_ip} to {dst_ip}")
# Use regex to find the Host and Path
host = re.search(r"Host: (.*?)\r\n", payload)
path = re.search(r"GET (.*?) HTTP", payload)
if host and path:
host = host.group(1)
path = path.group(1)
print(f" [+] Request for: http://{host}{path}")
# Also look for User-Agent
user_agent = re.search(r"User-Agent: (.*?)\r\n", payload)
if user_agent:
print(f" [+] User-Agent: {user_agent.group(1)}")
except Exception as e:
# Handle any potential decoding errors
# print(f"Error processing packet: {e}")
pass
# --- Main sniffing loop ---
BPF_FILTER = "tcp and port 80"
print(f"Starting HTTP Payload Sniffer (filter: '{BPF_FILTER}')... Press Ctrl+C to stop.")
sniff(store=0, prn=process_packet, filter=BPF_FILTER)
Run this script (with sudo) and visit http://httpforever.com/ again. Look at your terminal!

This is a massive moment. You are no longer just seeing traffic; you are interpreting it. You’ve parsed an application-layer protocol (HTTP) from raw packet data. This is the exact same technique malicious actors use to steal unencrypted session cookies or passwords from public Wi-Fi.
This also teaches us an important lesson: This is why HTTPS is so important. If you ran this same script with filter="tcp and port 443" (HTTPS), the packet[Raw].load would be a meaningless jumble of encrypted garbage. And that is a very good thing.
Case Study 2: Sniffing DNS Queries
Let’s try another one. Every time you visit a website, your computer first has to ask a DNS (Domain Name System) server, “What is the IP address for google.com?”
This communication almost always happens over UDP port 53. And Scapy, being brilliant, has a built-in DNS layer.
Create sniffer_v6_dns.py:
from scapy.all import *
def process_packet(packet):
"""
This function will be called for each packet sniffed.
"""
# We know it's UDP port 53, so it has IP, UDP, and (probably) DNS
# Check if it *is* a DNS packet
if packet.haslayer(DNS):
# A DNS packet has a 'qr' (Query/Response) field.
# 0 = Query (from client)
# 1 = Response (from server)
# We only care about the queries (qr=0)
if packet[DNS].qr == 0:
# Check if it has a query record (DNSQR)
if packet.haslayer(DNSQR):
# .qname is the "query name"
domain = packet[DNSQR].qname.decode('utf-8')
src_ip = packet[IP].src
print(f"[+] DNS Query from {src_ip}: {domain}")
# --- Main sniffing loop ---
BPF_FILTER = "udp and port 53"
print(f"Starting DNS Sniffer (filter: '{BPF_FILTER}')... Press Ctrl+C to stop.")
sniff(store=0, prn=process_packet, filter=BPF_FILTER)
Run this (with sudo). Open a new tab in your browser and visit any website you haven’t visited in a while (to ensure it’s not cached).
You will see the DNS query in your terminal before the website even starts to load.

This is incredibly powerful. A DNS sniffer can be the basis for a network-wide ad blocker (by seeing queries for ads.google.com and blocking them) or a parental control tool (by seeing queries for adult sites).
Saving and Reading Packets (PCAP)
Sometimes, you don’t want to analyze in real-time. You just want to capture everything during a specific event (like a program crashing) and analyze it later, perhaps in Wireshark.
The standard format for saving packet captures is PCAP (.pcap or .pcapng).
Writing to a PCAP File
Scapy makes this trivial. There are two main ways.
Method 1: Sniff then Write (Good for short captures) You can tell sniff() to return the packets (the default store=1) and then use the wrpcap() (Write PCAP) function.
# pcap_writer.py
from scapy.all import *
print("Starting 10-second capture... (Browse the web!)")
# Sniff for 10 seconds
packets = sniff(timeout=10)
print(f"Capture complete. Captured {len(packets)} packets.")
# Write them to a file
wrpcap("my_capture.pcap", packets)
print("Packets saved to 'my_capture.pcap'")
print("Try opening this file in Wireshark!")
Run this, browse for 10 seconds, and you’ll have a new file: my_capture.pcap.
Method 2: Real-time Appending (Better for long captures) Method 1 is bad for long captures as it holds everything in RAM. A better way is to append to the file in real-time inside our prn callback.
# pcap_appender.py
from scapy.all import *
PCAP_FILE = "live_capture.pcap"
def process_and_save(packet):
"""
This function will be called for each packet sniffed.
It appends the packet to our PCAP file.
"""
wrpcap(PCAP_FILE, packet, append=True)
print(f"Starting live capture... appending to {PCAP_FILE}. Press Ctrl+C to stop.")
sniff(store=0, prn=process_and_save)
Run this script, and it will continuously append to live_capture.pcap without filling up your RAM. This is how tcpdump works.
Reading from a PCAP File
This is where the magic happens. You can take any PCAP file—one you just made, one you downloaded from the internet, one from Wireshark—and “feed” it to Scapy.
The function is rdpcap() (Read PCAP).
Let’s say we have our my_capture.pcap from Method 1. We can now write a script to analyze it offline.
We can even reuse our DNS sniffer function from sniffer_v6_dns.py!
# pcap_reader.py
from scapy.all import *
# --- Copy-paste our function from the DNS sniffer ---
def process_dns_packet(packet):
if packet.haslayer(DNS) and packet[DNS].qr == 0:
if packet.haslayer(DNSQR):
domain = packet[DNSQR].qname.decode('utf-8')
src_ip = packet[IP].src
print(f"[+] Found DNS Query from {src_ip}: {domain}")
# --- Main analysis ---
PCAP_FILE = "my_capture.pcap" # Change this to your pcap file
print(f"Reading packets from {PCAP_FILE}...")
try:
# rdpcap() reads the entire file into a list in memory
packets = rdpcap(PCAP_FILE)
except FileNotFoundError:
print(f"Error: File not found: {PCAP_FILE}")
exit()
print(f"Read {len(packets)} packets. Analyzing for DNS queries...")
# Now just loop through the list!
dns_count = 0
for packet in packets:
# We can't use a BPF filter here, so we do Python-side filtering
if packet.haslayer(UDP) and packet.haslayer(DNS):
process_dns_packet(packet)
dns_count += 1
print(f"\nAnalysis complete. Found {dns_count} DNS queries.")
Run this script (no sudo needed! We’re just reading a file). It will load your PCAP, loop through every single packet, and run your DNS analysis function on it.
This is a professional workflow. You separate capture (which requires sudo and is time-sensitive) from analysis (which doesn’t require sudo and can be run at any time). This allows you to perfect your analysis scripts by running them over and over on the same dataset.

Beyond Sniffing: Crafting and Sending
This is the chapter that separates Scapy from every other tool. So far, we’ve only been listening (passive). Now, we’re going to talk (active).
Scapy lets you build any packet you can dream of from scratch, and send it.
The Send Functions: send() vs. sendp()
send(packet): This is a Layer 3 (IP) function. You give it a packet (e.g.,IP()/TCP()), and Scapy figures out the routing and Layer 2 (Ethernet) headers for you. It’s smart and easy.sendp(packet): This is a Layer 2 (Ethernet) function. You must give it a full Ethernet frame (e.g.,Ether()/IP()/TCP()). It’s lower-level and faster. You often have to specify theiface(interface).- Rule of Thumb: Use
send()when you’re routing to the internet. Usesendp()when you’re talking to devices on your local network (LAN).
The Packet “Stack”: The / Operator
In Scapy, you build packets by “stacking” layers with the / operator. It’s beautiful:
# An ICMP (ping) packet to Google
pkt = IP(dst="8.8.8.8") / ICMP()
# A DNS query
pkt = IP(dst="1.1.1.1") / UDP(dport=53) / DNS(qd=DNSQR(qname="google.com"))
# A TCP SYN packet (the start of a connection)
pkt = IP(dst="example.com") / TCP(dport=80, flags="S")
Scapy fills in all the boring parts (source IP, source port, lengths, checksums) automatically.
Example 1: A Custom Ping
Let’s send a single ICMP (ping) packet.
# sender_ping.py
from scapy.all import *
print("Building a custom ping packet...")
# We build it at Layer 3. Scapy will handle Layer 2.
pkt = IP(dst="8.8.8.8") / ICMP()
print("Packet to send:")
pkt.show()
print("\nSending packet...")
# send() is Layer 3, no return value
send(pkt)
print("Packet sent!")
Run this (with sudo! Sending packets also requires root). It will just… run. It doesn’t print a “reply.”
To see the reply, we need a function that sends and receives: sr() and srp().
sr()(Send/Receive): Layer 3.srp()(Send/Receive Packets): Layer 2.
# sr_ping.py
from scapy.all import *
pkt = IP(dst="8.8.8.8") / ICMP()
print("Sending and awaiting reply...")
# sr1() sends *one* packet and waits for the *first* reply
ans = sr1(pkt, timeout=2) # 2-second timeout
if ans:
print("\nReply received!")
ans.show()
else:
print("\nNo reply (timeout).")
Run this (with sudo). You’ll see it send the packet, and then, a second later, print the full ICMP Echo Reply it got back from Google’s server!
Example 2: LAN Host Discovery (An ARP Scanner)
This is one of the most useful scripts you can have. “What other devices are on my network?” We can answer this by sending an ARP (Address Resolution Protocol) request to every possible IP on our network and seeing who replies.
ARP is a Layer 2 protocol. It’s a broadcast “shout” that says, “Who has the IP address 192.168.1.1? Tell me, 192.168.1.10.” The device with that IP will reply, “I have 192.168.1.1! My MAC address is aa:bb:cc:dd:ee:ff.”
We can do this for our entire subnet (e.g., 192.168.1.0/24, which means 192.168.1.1 to 192.168.1.254).
Create lan_scanner.py:
from scapy.all import *
# Change this to your network's subnet
# Find it with 'ifconfig' (e.g., 192.168.0.0/24)
TARGET_SUBNET = "192.168.1.0/24"
# You must get this right!
# If your IP is 10.0.0.5, your subnet is "10.0.0.0/24"
print(f"Scanning LAN for subnet {TARGET_SUBNET}...")
# We need to build a packet at Layer 2
# 1. Ethernet "broadcast" frame (dst=ff:ff:ff:ff:ff:ff)
eth_frame = Ether(dst="ff:ff:ff:ff:ff:ff")
# 2. ARP request packet
# pdst = "packet destination" (the IP range we want to scan)
arp_req = ARP(pdst=TARGET_SUBNET)
# 3. Stack them
broadcast_packet = eth_frame / arp_req
# 4. Send and receive (srp)
# srp() is for Layer 2. It returns two lists:
# ans = answered packets
# unans = unanswered packets
# We set a 2-second timeout and verbose=0 to hide Scapy's noise.
ans, unans = srp(broadcast_packet, timeout=2, verbose=0)
print("\nScan Complete. Hosts found:")
print("IP Address\t\tMAC Address")
print("-----------------------------------------")
# The 'ans' list is a list of (sent, received) tuples.
# We iterate through it and pull out the data we need.
hosts = []
for sent_packet, received_packet in ans:
ip = received_packet[ARP].psrc # Sender's IP
mac = received_packet[Ether].src # Sender's MAC
hosts.append({'ip': ip, 'mac': mac})
# Sort by IP for a clean list
hosts.sort(key=lambda x: [int(y) for y in x['ip'].split('.')])
for host in hosts:
print(f"{host['ip'].ljust(16)}\t{host['mac']}")
print(f"\nFound {len(hosts)} hosts.")
Before you run this, find your subnet! Run ifconfig (macOS/Linux) or ipconfig (Windows). If your IP is 192.168.1.45, your subnet is 192.168.1.0/24. If your IP is 10.0.0.23, your subnet is 10.0.0.0/24. You must change TARGET_SUBNET or it won’t work.
Now, run it (with sudo!).

This is a complete, working, useful network utility. This is the first step in “network mapping.”
Ethical Warning (Reiteration): This ARP scan is active. You are sending packets. This is “louder” than sniffing. Doing this on a network you don’t own (like a café) is a very bad idea and will get you noticed (and possibly in trouble). It looks like the beginning of an attack. Only run this on your home network.
Building a Complete Tool (Putting It All Together)
We have a lot of small scripts. Let’s combine our best ideas into one, professional-grade tool.
A “real” tool has:
- Command-line arguments (to change settings without editing code).
- A clean
main()function. - A structured callback for processing.
We use Python’s built-in argparse library to handle command-line arguments.
Create py-sniffer.py. This is our “final boss” script.
#!/usr/bin/env python3
# Make the script executable
import argparse
from scapy.all import *
# --- Our Packet Processing Callback ---
# We'll re-use the "smarter sniffer" logic from v3
def process_packet(packet):
"""
This function will be called for each packet sniffed.
"""
# Check if the packet has an IP layer
if packet.haslayer(IP):
src_ip = packet[IP].src
dst_ip = packet[IP].dst
proto = packet[IP].proto
if proto == 6: # 6 is the protocol number for TCP
if packet.haslayer(TCP):
src_port = packet[TCP].sport
dst_port = packet[TCP].dport
flags = packet[TCP].flags
print(f"[TCP] {src_ip}:{src_port} -> {dst_ip}:{dst_port} [Flags: {flags}]")
elif proto == 17: # 17 is the protocol number for UDP
if packet.haslayer(UDP):
src_port = packet[UDP].sport
dst_port = packet[UDP].dport
print(f"[UDP] {src_ip}:{src_port} -> {dst_ip}:{dst_port}")
elif proto == 1: # 1 is the protocol number for ICMP
if packet.haslayer(ICMP):
print(f"[ICMP] {src_ip} -> {dst_ip}")
else:
print(f"[IP] {src_ip} -> {dst_ip} [Proto: {proto}]")
# We can add more dissectors here!
elif packet.haslayer(ARP):
print(f"[ARP] {packet[ARP].psrc} is at {packet[ARP].hwsrc}")
# ... etc.
# --- Main Program ---
def main():
# 1. Create the argument parser
parser = argparse.ArgumentParser(
description="A simple Python network sniffer using Scapy.",
epilog="Example: sudo python3 py-sniffer.py -i eth0 -f 'tcp and port 80' -c 10"
)
# 2. Add arguments
parser.add_argument(
"-i", "--interface",
type=str,
help="Network interface to sniff on (e.g., 'eth0', 'wlan0')."
)
parser.add_argument(
"-f", "--filter",
type=str,
default=None,
help="BPF filter string (e.g., 'tcp and port 80')."
)
parser.add_argument(
"-c", "--count",
type=int,
default=0,
help="Number of packets to capture (0 for unlimited)."
)
# 3. Parse the arguments
args = parser.parse_args()
# 4. Validate and use arguments
iface = args.interface
bpf_filter = args.filter
count = args.count
if iface:
print(f"[*] Sniffing on interface: {iface}")
else:
print("[*] Sniffing on default interface (conf.iface)")
if bpf_filter:
print(f"[*] Applying BPF filter: {bpf_filter}")
if count > 0:
print(f"[*] Capturing {count} packets...")
else:
print("[*] Capturing packets... (Press Ctrl+C to stop)")
# 5. Build the sniff() call
sniff_args = {
'store': 0,
'prn': process_packet,
'count': count
}
if iface:
sniff_args['iface'] = iface
if bpf_filter:
sniff_args['filter'] = bpf_filter
# 6. Run the sniffer
try:
sniff(**sniff_args)
except Exception as e:
print(f"\n[!] An error occurred: {e}")
print("[!] Hint: Do you need to run as root/admin?")
print("[!] Hint: Is the interface name correct? (use 'ifconfig' or 'ipconfig')")
# Make sure we only run main() when the script is executed directly
if __name__ == "__main__":
main()
This script is a massive leap forward. It’s now a reusable, flexible tool.
Look at how we can use it from the command line (with sudo!):
Usage Example 1: Sniff 10 TCP packets on interface eth0
sudo python3 py-sniffer.py -i eth0 -f "tcp" -c 10
Usage Example 2: Sniff all DNS queries on the default interface
sudo python3 py-sniffer.py -f "udp and port 53"
Usage Example 3: Sniff everything except ARP and mDNS
sudo python3 py-sniffer.py -f "not (arp or (udp and port 5353))"

You now have a single, powerful script that can do almost everything our previous 6 scripts could, just by changing the command-line flags. This is how professional tools are built.
Common Pitfalls and Troubleshooting
You will run into errors. Here are the most common ones and how to fix them.
1. Error: Permission denied / Operation not permitted / Socket error
- Cause: You forgot to run as root/administrator. Sniffing is a privileged operation.
- Solution: Use
sudo python3 ...on macOS/Linux. On Windows, right-click your terminal (PowerShell/CMD) and “Run as Administrator.”
2. Error: No such device / Interface not found
- Cause: You specified an interface with
-ithat doesn’t exist. - Solution: Run
ifconfig(macOS/Linux) oripconfig(Windows) to get the exact name of your active interface. It might been0,eth0,wlan0,Wi-Fi, orEthernet. Names are case-sensitive.
3. On Windows: “Scapy is not working!” / No pcap provider...
- Cause: Npcap is not installed, or it wasn’t installed in “WinPcap compatible mode.”
- Solution: Re-download and re-install Npcap. During installation, read every screen and make sure the “WinPcap API-compatible Mode” box is checked. Restart your terminal.
4. “My sniffer is slow, or it’s dropping packets!”
- Cause: Your
process_packetfunction is too slow. You’re doing heavy processing (like writing to a database, or making an API call) inside the callback. The kernel’s packet buffer overflows while it waits for your function to finish. - Solution: Your
prncallback must be lightning fast. Its only job should be to grab the data and put it into aqueue. A separate worker thread should read from that queue and do the slow, heavy processing. This (multithreading) is an advanced topic, but it’s the key to high-performance sniffing.
5. “I can’t sniff my own localhost (127.0.0.1) traffic!”
- Cause: This is normal. Loopback traffic (programs talking to themselves on the same machine) often doesn’t go through the full network stack and may not be “seen” by a sniffer on a physical interface.
- Solution: You often have to sniff on a special “loopback” interface. In Scapy, you can try
sniff(iface="lo0")(macOS/Linux) orsniff(iface="Npcap Loopback Adapter")(Windows).
The Ethical Hacker’s Code (A Final, Serious Word)
We have built an incredibly powerful tool. We’ve gone from zero to a command-line utility that can intercept, dissect, and even forge network traffic.
This power comes with immense responsibility. The line between a “network administrator” and a “cybercriminal” is not in the tool, but in permission.

White Hat (Legal, Ethical, Good)
- You run your LAN scanner on your own home network to see if your neighbor has connected to your Wi-Fi.
- You run your DNS sniffer on your own network to see if your smart TV is sending data to strange domains.
- You are hired by a company, and they give you a signed contract (a “get out of jail free card”) to run your tools on their network to find security holes before the bad guys do. This is Penetration Testing.
Black Hat (Illegal, Unethical, Bad)
- You go to a coffee shop, connect to the public Wi-Fi, and run your HTTP payload sniffer. You steal the session cookie of the person next to you and log into their Facebook account. This is session hijacking.
- You run your ARP scanner on your school or work network to map it out. This is unauthorized reconnaissance and will (at minimum) get you fired or suspended.
- You (in a more advanced attack) combine your ARP scanner with packet forging to conduct an ARP Spoofing attack, tricking all devices on the LAN into sending their traffic through your laptop (a “Man-in-the-Middle” attack) so you can sniff their HTTPS traffic. This is a felony in many places.
The Golden Rule: If you do not own the device, own the network, or have explicit, written permission from the owner, DO NOT RUN THESE TOOLS.
This tutorial is designed to make you a better developer, a more curious engineer, and a more effective defender. By understanding how these “attacks” work, you are in the best possible position to prevent them. You now understand why unencrypted HTTP is so dangerous. You now see why public Wi-Fi is a risk. That knowledge is the first and most important step to building a more secure world.
Conclusion and Your Next Steps
If you’ve made it this far, you are in the top 1% of aspiring tech professionals. You haven’t just read theory; you’ve built it.
You have learned:
- The OSI and TCP/IP models, practically.
- How to set up a clean, professional Python environment.
- How to use Scapy to
sniff()packets in real-time. - How to use BPF to filter traffic efficiently.
- How to dissect
IP,TCP,UDP,DNS, andHTTPpackets. - How to read and write industry-standard
PCAPfiles. - How to forge and send your own packets with
srp()to build a LAN scanner. - How to combine all this into a single, polished
argparsetool. - …and, most importantly, the ethical bright-line of permission.
You don’t just have a script. You have a new, fundamental skill.
Where Do You Go From Here?
This is just the beginning. Here are some challenges to take your skills to the next level:
- Build a GUI: Take your
py-sniffer.pyscript and wrap it in a graphical user interface using Tkinter (built-in) or PyQt. Have a “Start” button, a “Stop” button, and a scrolling text box that shows theprocess_packetoutput. - Build an “Evil Twin” Detector: Use your ARP scanning logic. Run it every 60 seconds. Does a new device’s IP ever change its MAC address? Or does one MAC address suddenly claim multiple IPs? This is a classic sign of an ARP spoofing attack. Write a script to detect and alert you.
- Go Deeper on Protocols: Write Scapy dissectors for other protocols.
DHCP(UDP ports 67/68) is a great one. Can you see when a new device joins your network and asks for an IP? - Network Data Science: Use your
pcap_appender.pyto capture an hour of traffic. Userdpcap()to load it. Now, use the Pandas library to analyze it. What are the “Top 10 Talkers” (most active IPs)? What’s the ratio of TCP vs. UDP traffic? - Multi-threaded Sniffer: As mentioned in the troubleshooting section, rebuild your sniffer to use a
queueand a separate worker thread for processing. This is the “professional” pattern for high-speed capture.
You started this guide curious about a “black box.” You’ve finished it with the X-ray vision to see inside. The network is no longer magic; it’s a system. And now, you’re one of the people who understands it.
Happy (and ethical) sniffing!








