Step 80. Introduction to Scapy — The Handmade Packet Workshop
Level 1 — Programming and the Computer’s Insides | Difficulty ★★★★☆ | Estimated time: 4 hours
Prerequisites: Steps 77–79 complete; you know socket communication and the principle of port scanning. You can use a Linux (WSL or Ubuntu) terminal.
- What you need: a Linux terminal (WSL included) and Python. Linux is the standard for packet send/receive practice — you’ll experience the reason yourself in 3-3.
- Caution: ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.
Until now we talked using the telephone called a socket. Convenient — but the only thing we could decide was "what to say"; the envelope (packet) holding those words was made for us automatically by the operating system. Scapy is the workshop where we make that envelope with our own hands. We stack packets like Lego blocks, layer by layer, modify them, send them, and receive answers. You must be able to make abnormal packets to understand the attacks that happen when such packets arrive — today’s handwork is tomorrow’s analytical power.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Read from
show()output that a packet is a multi-layer structure stacked as Ethernet/IP/TCP/data - Assemble packets with Scapy’s
/operation and change field values - Send a SYN packet with
sr1()and judge the port state from the answer’s flags (SA/RA) - Explain why crafting raw packets requires special privileges and components (Npcap/Linux)
- Explain the fact that source-address forgery (spoofing) takes one line — and its weight
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Python 3 + Scapy (pip install). Sending/receiving is done on Linux/WSL |
| Today’s functions | IP()/TCP()/ICMP(), / (layer stacking), show(), summary(), sr1(), send(), sniff() |
| Concepts needed | Packet layers, the 3-way handshake (SYN/SYN-ACK/ACK/RST), flags, administrator privileges |
| Today’s output | A handmade SYN scanner syn_scan.py — a packet-level scout |
2-1. A Packet Is Boxes Stacked upon Boxes
Network data is wrapped in "layers." The letter paper (data) goes into an envelope (TCP), that goes into a bigger envelope (IP), and that again into a delivery envelope (Ethernet). Each layer carries the information needed for that layer’s job (a header).
- Ethernet: delivery inside the same building (MAC addresses)
- IP: delivery between buildings (IP addresses)
- TCP: delivery that takes care of order and acknowledgment, all the way to the exact room (port)
- Data: the real contents
In Scapy, this stacking is expressed with a slash (/). IP()/TCP() means "a TCP envelope inside an IP envelope."
2-2. SYN and the Three-Part Handshake
In Step 79 we knocked on ports with connect. Underneath, a three-part handshake (3-way handshake) was actually taking place.
- Client → server: SYN ("I’d like to connect")
- Server → client: SYN-ACK ("Fine, let’s connect")
- Client → server: ACK ("Confirmed")
If the port is closed, step 2 is replaced by RST-ACK ("there’s no such room"). That is exactly last chapter’s "immediate refusal." Today we assemble this SYN packet by hand, send it, and judge by looking only at step 2 of the handshake — not completing the connection. This is the true identity of the "SYN scan" we only heard the name of last time.
2-3. Why Special Privileges and Components Are Needed
If a socket is an "automatic wrapper of standard envelopes," Scapy makes "raw envelopes." The operating system doesn’t permit such raw packets to just anyone — forged packets can foul the network. On Linux, administrator-level privileges are needed; on Windows, a separate component called Npcap. Today you’ll meet this fact not as theory but as an error message.
3. Follow Along
3-1. Installing — One pip Line, But
Input (Windows/Linux alike)
pip install scapy
Output (measured 2026-09-09):
Successfully installed scapy-2.7.0
Verify the installation:
python -c "import scapy; print(scapy.__version__)"
Output (measured 2026-09-09):
2.7.0
How to read it: the installation itself is an ordinary single pip line. Note, however, that recent Linux distributions (WSL Ubuntu included) block direct pip installs to protect the system Python — in our measurement too it was refused with an externally-managed-environment notice (2026-09-09). In that case, the proper way is to create a virtual environment and install inside it:
python3 -m venv ~/scapy-venv
~/scapy-venv/bin/pip install scapy
Why: it’s important to read this not as "blocked" but as "being protected." The system Python is a component of the operating system, locked so it can’t be changed carelessly. A virtual environment is the official passage that detours without breaking that lock.
3-2. Assembling a Packet — Stacking Legos
We continue in Python’s interactive mode (after typing python, at the >>>). Assembly works on Windows too.
Input
from scapy.all import IP, TCP, ICMP
pkt = IP(dst="127.0.0.1")/TCP(dport=9999, flags="S")
pkt.show()
Output (measured 2026-09-09):
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = tcp
chksum = None
src = 127.0.0.1
dst = 127.0.0.1
\options \
###[ TCP ]###
sport = ftp_data
dport = 9999
seq = 0
ack = 0
dataofs = None
reserved = 0
flags = S
window = 8192
chksum = None
urgptr = 0
options = []
How to read it: below ###[ IP ]### comes the IP layer, below that the TCP layer — printed exactly as stacked by the slash. We set only three values — dst, dport, flags — and Scapy filled the rest with defaults. Fields shown as None, like len and chksum, are calculated and filled by Scapy when actually sent. sport showing as ftp_data is Scapy kindly translating the default port 20 into its service name.
Why: this is the stage of seeing that "a packet is a form filled out box by box." An attack packet is merely this form’s boxes twisted and filled with intent — which is why knowing the form is the start of analysis.
3-3. Predict — What Happens If You Send It from Windows?
Time to predict. If you send the packet we just made straight from Windows (with sr1), what happens?
Check yourself (measured 2026-09-09, Windows + no Npcap):
ValueError: Interface 'Microsoft KM-TEST Loopback Adapter' not found !
How to read it: assembly worked, but dispatch didn’t. To emit raw packets on Windows you need the separate Npcap component; without it, the interface can’t be found and it stops like this. (The WARNING: No libpcap provider available warning that appeared at import time is the same signal — measured 2026-09-09.)
Why: that’s why Linux (WSL/Ubuntu) is the standard for this chapter’s send/receive practice. From here we move to the WSL terminal — the environment where you installed scapy into the virtual environment in 3-1.
3-4. Send and Receive an Answer — A Handmade SYN Scan (Linux)
We continue in WSL. First, send to a closed port.
Input
from scapy.all import IP, TCP, sr1
pkt = IP(dst="127.0.0.1")/TCP(dport=65000, flags="S")
ans = sr1(pkt, timeout=2, verbose=0)
print("Answer flags:", ans[TCP].flags if ans else "no answer")
Output (measured 2026-09-09):
Answer flags: RA
How to read it: sr1 is a function that "sends and receives one answer." RA (RST-ACK) is the immediate reply "there’s no such room." The signal we met last chapter as the Connection refused error — today we meet it head-on by its packet name.
This time, send to an open port. In another WSL terminal, deliberately open one door.
Input (terminal 2)
python3 -m http.server 9999 --bind 127.0.0.1
Input (terminal 1, interactive)
pkt2 = IP(dst="127.0.0.1")/TCP(dport=9999, flags="S")
ans2 = sr1(pkt2, timeout=2, verbose=0)
print("Answer flags:", ans2[TCP].flags if ans2 else "no answer")
Output (measured 2026-09-09):
Answer flags: SA
How to read it: SA (SYN-ACK) is "fine, let’s connect" — meaning the port is open. If the answer’s flags are SA it’s open, RA it’s closed, and no answer means filtered. What Step 79’s connect_ex did, we are now doing directly at the packet level.
Why: if last chapter’s scanner "placed the call all the way through," today’s method "throws only the first greeting and judges by the tone of the reply." The reason a SYN scan — which leaves fewer logs by not completing the connection — is called a quiet scan now fits in your hand.
3-5. The Trap of flags Comparison — Display and Data Type Differ
Let’s look precisely at the flags we just used for judgment.
Input
f = ans2[TCP].flags
print("Value:", f, "| Type:", type(f).__name__)
print("String compare SA:", f == "SA")
print("String compare RA:", f == "RA")
Output (measured 2026-09-09, for ans where RA came back):
Value: RA | Type: FlagValue
String compare SA: False
String compare RA: True
How to read it: to the eye it reads "RA," but its substance is a special object called FlagValue. In recent Scapy (2.7.0 measured) string comparison works, but behavior can differ by version, so the habit of converting to a string for judgment, like "S" in str(f), is safer.
Why: a representative case where "what shows on the screen" and "what the program is holding" differ. The habit of printing print(type(x)) when debugging saves you time in traps like this.
3-6. ICMP — ping by Handwork Too
The ping command we use so often is, in the end, packets too. Let’s make one ourselves.
Input
ping = IP(dst="127.0.0.1")/ICMP()
ans3 = sr1(ping, timeout=2, verbose=0)
print("alive" if ans3 else "no response")
Output (measured 2026-09-09):
alive
How to read it: ICMP is a control protocol that "asks and answers about network state." What we sent was "are you alive? (echo request)" among them, and the answer that came back is "I’m alive (echo reply)." What the ping command made every time, today we made by hand.
3-7. Eavesdropping on Passing Packets — sniff
The opposite of making: "watching." sniff snatches packets brushing past the network card. Let’s generate ICMP on the loopback (lo) and snatch them.
Input (sniff_icmp.py)
import threading, time
from scapy.all import IP, ICMP, send, sniff
def noise():
time.sleep(0.5)
for _ in range(3):
send(IP(dst="127.0.0.1")/ICMP(), verbose=0)
threading.Thread(target=noise).start()
pkts = sniff(iface="lo", count=4, timeout=5)
for p in pkts:
print(p.summary())
Output (measured 2026-09-09):
Ether / IP / ICMP 127.0.0.1 > 127.0.0.1 echo-request 0
Ether / IP / ICMP 127.0.0.1 > 127.0.0.1 echo-request 0
Ether / IP / ICMP 127.0.0.1 > 127.0.0.1 echo-reply 0
Ether / IP / ICMP 127.0.0.1 > 127.0.0.1 echo-reply 0
How to read it: summary() condenses each packet into one line — the stacked layers (Ether / IP / ICMP) and "who > to whom" read plainly. Of the three requests and three replies, the first four were snatched.
Why: your computer is quietly exchanging packets even at this very moment. Being able to see this flow is the starting point of analysis — the same principle as the eyes of Wireshark, which you’ll learn soon.
3-8. Observation — Swapping the Source Address
Let’s confirm Scapy’s power and danger, just once, carefully. The target is 127.0.0.1 — my computer only.
Input
from scapy.all import IP, ICMP, send, sr1
fake = IP(src="10.9.9.9", dst="127.0.0.1")/ICMP()
send(fake, verbose=0)
print("Forged packet sent")
Output (measured 2026-09-09):
Forged packet sent
How to read it: we swapped src (the source) for a nonexistent address (10.9.9.9) and sent it. This is the one-line summary of spoofing (forgery). The reply goes to the wrong address (10.9.9.9), so it never reaches us — an ICMP sent from the same seat with a normal source did get an answer (measured 2026-09-09). That is exactly why a forged packet gets no reply.
Why: what was impossible with sockets (source forgery) becomes one line with raw packets. That’s why this tool demands special privileges, and that’s why it must never leave the lab. Stop at this confirmation.
4. Missions & Exercises
Mission — A Handmade SYN Scanner
Create syn_scan.py to perform the following (on Linux/WSL):
- Fix the target at 127.0.0.1 and write the port list [21, 22, 80, 443, 9999] into the code
- Build and send a SYN packet to each port, judge "open (SA) / closed (RA) / no response" from the answer’s flags, and print a table
- Deliberately open one door (
python3 -m http.server 9999 --bind 127.0.0.1) and confirm 9999 is caught as "open" - Compare side by side with Step 79’s connect-scanner results and check whether the same conclusion comes out
- At the end, one paragraph: "why a SYN scan is quieter than a connect scan," explained together with a picture of the three-part handshake
Exercises
Q1. Explain a packet’s layers (Ethernet/IP/TCP/data) together with each layer’s role.
Q2. State each step of the three-part handshake (3-way handshake), and how step 2 changes when the port is closed.
Q3. What does Scapy’s / operation mean? Unpack IP()/TCP()/Raw("hello") into words.
Q4. Name one thing that is impossible with socket programming (Steps 77–79) but possible with Scapy, and explain why this tool therefore requires special privileges.
5. Model Answers & Completion Criteria
Mission Model Answer
The skeleton of a handmade SYN scanner:
from scapy.all import IP, TCP, sr1
target = "127.0.0.1"
ports = [21, 22, 80, 443, 9999]
print(f"Target: {target} — confirm it's my lab!")
for port in ports:
pkt = IP(dst=target)/TCP(dport=port, flags="S")
ans = sr1(pkt, timeout=2, verbose=0)
if ans is None:
state = "no response (possibly filtered)"
else:
flags = str(ans[TCP].flags)
if "S" in flags and "A" in flags:
state = "open (SA)"
elif "R" in flags:
state = "closed (RA)"
else:
state = f"other ({flags})"
print(f"Port {port:>5}: {state}")
Execution results vary by environment. In the measured environment (2026-09-09), only 9999 — where http.server was running — was "open (SA)" and the rest were "closed (RA)."
An example answer for item 5: "A connect scan completes all three parts of the handshake — SYN → SYN-ACK → ACK — so a connection record remains in the service’s access log. A SYN scan judges by watching only up to step 2 (SYN-ACK or RST-ACK) and never completes the handshake, so no record that ‘a connection was established’ remains, making it relatively quiet."
How to verify: ① Scanning before and after starting http.server, does 9999’s verdict change "closed → open"? ② Does the conclusion agree with the Step 79 scanner? ③ Does the flags judgment avoid the string-comparison trap (3-5)? If all three are "yes," it’s complete.
Exercise Solutions
Q1 solution. Ethernet is delivery within the same network segment (MAC addresses), IP is delivery between networks (IP addresses), TCP is delivery that takes care of order and acknowledgment up to the destination program (port), and data is the actual contents inside. It’s a stacked structure — letter paper into an envelope, the envelope into a bigger envelope.
Q2 solution. ① The client sends SYN ("I’d like to connect"), ② the server answers with SYN-ACK ("fine"), and ③ the client finishes with ACK ("confirmed"). If the port is closed, ② changes to RST-ACK ("there’s no such room") — this is the true identity of last chapter’s Connection refused.
Q3 solution. / is "layer stacking." IP()/TCP()/Raw("hello") is "a packet with a TCP envelope inside an IP envelope, with the contents hello inside that." The left is the outer envelope, the right is inner.
Q4 solution. Source-address forgery (spoofing). With sockets the operating system fills the envelope’s outer address, so you can’t deceive it, but with Scapy we fill every box of the form, so one line does it (3-8 measurement). Because forged packets can foul the network, the operating system permits raw-packet crafting only to those with special privileges (administrator-level rights on Linux, Npcap on Windows).
Completion Criteria Checklist
- [ ] I can explain a packet’s layer structure (Ethernet/IP/TCP)
- [ ] I can assemble a SYN packet with Scapy and read the show() output
- [ ] I judged port states from the answer’s flags (SA/RA/no response)
- [ ] I can explain why sending/receiving doesn’t work on Windows (Npcap)
- [ ] I snatched passing packets with sniff and read their summaries
- [ ] Mission: I completed the handmade SYN scanner
6. Common Pitfalls & Fixes
Wall 1. pip install is refused (Linux)
Symptom (measured 2026-09-09):
error: externally-managed-environment
Cause: recent Linux distributions block direct installation to protect the system Python. Not a malfunction — a lock.
Fix: create a virtual environment and install inside it — python3 -m venv ~/scapy-venv, then ~/scapy-venv/bin/pip install scapy. Run with that virtual environment’s Python afterward too.
Wall 2. Packets won’t go out on Windows
Symptom (measured 2026-09-09):
WARNING: No libpcap provider available ! pcap won't be used
ValueError: Interface 'Microsoft KM-TEST Loopback Adapter' not found !
Cause: sending and receiving raw packets on Windows requires the separate Npcap component. The telltale sign is that assembly (show) works but dispatch (sr1/send) doesn’t.
Fix: Linux (WSL/Ubuntu VM) is the standard for this chapter’s send/receive practice. On Windows, practice only up to assembly, then move dispatch to WSL.
Wall 3. flags comparison disagrees with my eyes
Symptom: ans[TCP].flags == "SA" is False, yet it looks like SA.
Cause: Scapy’s flags are a special FlagValue object (measured 2026-09-09: Type: FlagValue). String-comparison behavior differs by version.
Fix: convert with str(ans[TCP].flags) and judge by containment, like "S" in .... When in doubt, print(type(x)) is the best diagnostic tool.
Wall 4. No answers come at all
Symptom: every port says only "no answer."
Cause: the target is off, a firewall filters everything, or the virtual machine’s network is disconnected.
Fix: first check signs of life with the handmade ICMP of 3-6. As in this chapter’s practice, fixing the target at 127.0.0.1 and deliberately opening one door (http.server) for verification lets you split environment problems from code problems.
Wall 5. sniff catches nothing
Symptom: you ran sniff and it’s quiet.
Cause: no catchable packets went past. If there’s no communication at all, nothing gets hooked either (in the 2026-09-09 measurement too, hands were empty before traffic was generated).
Fix: generate packets from a separate thread as in 3-7, or create traffic by opening any page in a browser. Also check the iface setting (Linux loopback is "lo").
7. Summary
Today’s Concepts
| Concept | One-line description |
|---|---|
| Packet layers | Multi-layer envelopes stacked as Ethernet/IP/TCP/data — Scapy stacks with / |
| 3-way handshake | Connection established via a three-part handshake: SYN → SYN-ACK → ACK |
| SYN scan | A quiet scan judging by watching only step 2 of the handshake (SA = open, RA = closed) |
| flags | The status-marking boxes of the TCP header — read as SA, RA, etc. |
| Spoofing | Source-address forgery — a one-line danger possible only with raw packets |
| Npcap / administrator privileges | The special components and privileges needed for raw-packet crafting |
Today’s Functions
| Function | What it does |
|---|---|
IP(dst=...)/TCP(dport=..., flags="S") |
Assemble a packet (stack layers with slashes) |
pkt.show() / pkt.summary() |
View the whole form / one-line summary |
sr1(pkt, timeout=2) |
Send and receive one answer |
send(pkt) |
Send only (doesn’t wait for an answer) |
sniff(iface=..., count=n) |
Snatch n passing packets |
The Instinct That Matters More Than Commands
In networking study, the skill gap splits not on "do you know the commands" but on "can you imagine what lies beneath the commands." Anyone can type ping. But today you made ping’s innards (ICMP echo) by hand, and you assembled a port scan’s innards (SYN and its answer) yourself. However excellent nmap is, what it sends is in the end the same packets you made today, and what a firewall filters is likewise these box-by-box forms. When a tool breaks, when output looks strange — if you’ve gained the habit of imagining "what packets must have flowed underneath," that is today’s real harvest.
And take the weight with you too. What you’ve been handed today is "a pen that can freely write every box of the letter called a packet." With this pen you can run legitimate tests (does my firewall filter abnormal packets well) — or do bad things. The difference is not the pen but permission. Every handmade packet that flew today was aimed at 127.0.0.1, your own computer alone — keeping that fence from now on is the qualification for using this tool.
Once every box is checked, Step 80 is complete. Click the checkbox in the sidebar to save your progress.