Module 06 — Network Programming¶
Type 9 · Tool-Build — build a port scanner and banner grabber on raw Python socket, parse a packet trace with scapy, and prove the scanner with a test pinning OPEN/CLOSED verdicts plus a SYN-ACK/RST check. Test only systems you own or have written permission to. (Secondary: Concept Autopsy — understand what happens at each layer of the wire, not just the tool output.) Go to the hands-on lab →
Last reviewed: 2026-06
Python for Security — understanding the wire is what separates a script from a tool and a tool from an audit.
In 60 seconds
Every network security tool — scanner, banner grabber, sniffer — sits on raw sockets. A TCP port
scanner is thirty lines (connect_ex, a short timeout, record the result); banner grabbing reads
the first bytes a service announces; scapy lets you forge, send, and parse packets layer by
layer. The code is the easy part. The discipline is operational: scan only what you own or have
written permission to test, and understand that filtered ≠ closed ≠ open — each response is
information.
Why this matters¶
Security tools that manipulate the network — scanners, banner grabbers, sniffers, honeypot
detectors — are all built on raw socket operations. Understanding how socket and scapy work
at this level means you can build your own, read the source of existing tools, and understand
exactly what a scanner is doing on the wire when you fire it at a target.
Objective¶
Write a port scanner and banner grabber in raw Python socket, then use scapy to capture and
parse a small packet trace — and prove the scanner with a test you wrote: an assertion that it
returns OPEN/CLOSED correctly for the fixed target port set, plus at least one SYN-ACK/RST check
from the capture. Building the scanner and committing a test that pins its verdicts are equal
halves — understanding what is happening at each layer, not just reading tool output.
The core idea¶
A TCP port scanner is thirty lines of Python. socket.connect_ex(host, port) returns 0 on
success and an error code on failure; iterate the port list, record the result, close the
connection. The hard part is not the code — it is the operational discipline: always scan only
targets you own or have written permission to test, set a short timeout (socket.settimeout(1.0))
so the scan doesn't hang indefinitely, and understand that a filtered port (firewall drops the
packet) behaves differently from a closed port (RST returned) — which is information in itself.
The mental model
The scanner code is trivial; the value is reading the responses. A RST means closed, a
SYN-ACK means open, and silence (the packet dropped) means filtered — and that silence is itself
intel about a firewall. You're not running a tool, you're interrogating a host one packet at a time.
Banner grabbing is the step after port enumeration: once you know a port is open, send a probe
and read the first bytes the service returns. Most services announce themselves — SSH says
SSH-2.0-OpenSSH_8.9, HTTP says the server header, SMTP says 220 mail.example.com. You do
not need nmap for this; socket.recv(1024) and a .decode("utf-8", errors="replace") gets
you there. Be aware that some services send nothing until you speak first (FTP is prompt-first;
raw recv on an HTTPS port returns TLS hello bytes, not plaintext).
scapy is the Swiss Army knife of Python networking: it can forge arbitrary packets at any layer,
send them, capture responses, and parse captures. The core mental model is that every packet is
a stack of layers — Ether / IP / TCP / Raw — and you can read or set any field at any layer.
sniff(count=10, filter="tcp port 22") captures 10 packets matching a BPF filter. wrpcap() and
rdpcap() read and write .pcap files, so scapy can post-process captures from Wireshark or
tcpdump. The power and the risk are the same: scapy will send what you tell it to send,
including malformed packets — there is no safety net.
The gotcha
A port scanner pointed at the wrong subnet is an incident, and scapy will send whatever you tell
it — malformed packets included — with no guardrail. Scan only systems you own or have written
permission to test; every exercise here runs against the compose echo server or loopback, never an
external host. Outside an authorized scope, scanning is illegal regardless of intent.
Go deeper: the scapy layer model and recv-first services
Every packet is a stack of layers — Ether / IP / TCP / Raw — and you read or set any field at
any layer with the / operator; sniff(filter="tcp port 22") and rdpcap()/wrpcap() let you
capture and post-process traces from Wireshark or tcpdump. Note that some services don't announce
until you speak first: FTP is prompt-first, and raw recv on an HTTPS port returns TLS hello
bytes, not plaintext.
Learn (~3 hrs)¶
Socket programming (~1 hr)
- Socket Programming in Python — Real Python — the definitive walkthrough; read through "TCP Sockets" and "Multi-Connection Client and Server"; skip the async section for now.
- socket — Low-level networking interface (Python docs) — reference; focus on socket(), connect_ex(), settimeout(), recv(), sendall().
Scapy (~2 hrs)
- Scapy — Official documentation, "Building packets" — the "Quick start" and "Building packets" sections; focus on the layer stack model (/ operator).
Key concepts¶
socket.connect_ex()for non-throwing port probes;settimeout()to prevent hangs- Banner grabbing: recv-first vs probe-first services
- Filtered vs closed vs open: what each TCP response means
- Scapy layer model:
Ether / IP / TCP / Raw - BPF filters in
scapy.sniff()— same syntax as tcpdump - Authorization: scan only what you own or have written permission for
- Verify by test, not by eye: a learner-written test that asserts OPEN/CLOSED per the fixed target ports (plus a SYN-ACK/RST check) — the ownership half, not a diff against
make demo
AI acceleration¶
Ask a model to write a port scanner. It will produce a working one. Then ask it to add a
--timeout argument and handle the ConnectionRefusedError, TimeoutError, and
OSError cases explicitly. The error handling is where the model's first draft usually
has gaps — and a scanner that crashes on a filtered port is not a scanner.
AI caveat
A model produces a working scanner immediately, but its first draft typically skips the error
cases — ConnectionRefusedError, TimeoutError, OSError on a filtered port. Push it to handle
each explicitly; a scanner that crashes on the first filtered port can't survive a real subnet.
Check yourself
- On the wire, how do open, closed, and filtered ports differ — and why is "filtered" still useful intel?
- Why set
socket.settimeout()on a scan, and what breaks if you don't? - In
scapy, what does the/operator do when you writeIP()/TCP()?
Comments
Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).