This is the last article of Unit 1. It gathers everything into one place: a picture of how the pieces fit, a quick-reference you can keep beside your keyboard, the self-study questions with worked answers, and the three lab exercises that put every idea to work in a laboratory you control.
Key takeaway: Unit 1 in one paragraph
Unit 1 gave you both halves of the foundation. From Python: variables, lists, dictionaries, functions, classes and file handling, the building blocks of every script ahead. From networking: IP addresses and ports, the TCP and UDP protocols, the three-way handshake, and the socket API that turns all of it into code. Every tool in the next four units is assembled from these parts.
How the pieces fit together
Quick reference: the socket calls
| Call | What it does |
|---|---|
socket.socket(AF_INET, SOCK_STREAM) | Create a TCP socket (use SOCK_DGRAM for UDP) |
.connect((host, port)) | Client: open a connection to a server |
.bind((host, port)) | Server: claim a local address and port |
.listen(n) | Server: begin queuing incoming connections |
.accept() | Server: wait for and accept one connection |
.send() / .recv() | Exchange bytes over a connected TCP socket |
.sendto() / .recvfrom() | Exchange datagrams over UDP, address included |
.close() | Hang up and release the socket |
socket.gethostbyname(name) | Resolve a hostname to an IP address |
Quick reference: the Python you used
Types
str text, int whole numbers, float decimals, bool True or False, list ordered, dict by key, tuple fixed pairs.
Operators
// whole-number division, % remainder, in membership, is None missing value, and or not logic.
Strings
.split() cut, .strip() trim, .lower() normalise, .join() rebuild, .startswith() test. f-strings for output.
Control flow
if / elif / else decide, for known list, while unknown rounds, range(a, b) sweep, def a recipe card.
Files
with open(...) always closes. Modes "r" read, "w" overwrite, "a" append, "rb" and "wb" bytes. json.dump and json.load.
Safety
try / except / finally. Catch by name. Log before you re-raise. Always set a timeout on a socket.
What you can now do
Write
Short Python scripts with variables, lists, dictionaries, loops, functions, classes and files.
Explain
IP address versus port, the four layers, TCP versus UDP, and the three-way handshake.
Build
TCP and UDP clients and servers, and a threaded server with swappable handlers.
Investigate
Resolve names, read banners, parse them into software and version, and say why a banner is only a clue.
Protect your scripts
Wrap network code so that one bad host never ends a scan.
Self-study questions
Try each one yourself before opening the answer.
Question 1: Using the office-building analogy, explain in your own words the difference between an IP address and a port.
The IP address is the street address of the building: it delivers a packet to the right computer. A port is one numbered door inside that building, up to 65,535 of them: it delivers the packet to the right service. A web server waits behind door 80 or 443, SSH behind 22, mail behind 25.
Question 2: Why must a security script usually run inside a virtual environment rather than installing packages into the system Python?
A virtual environment gives each project its own copy of Python and its own packages. Installing into the system Python can silently change versions that other projects (or the operating system itself) depend on, and on current Kali, Ubuntu and Debian releases it is refused outright. A virtual environment also makes a project's dependencies easy to record in requirements.txt and recreate elsewhere.
Question 3: Walk through the TCP three-way handshake step by step, and explain why a port scanner can rely on it to decide that a port is open.
The client sends SYN ("can you hear me?"). The server replies SYN-ACK ("yes, can you hear me?"). The client sends ACK ("yes"), and the connection is established. A server only completes the greeting when a service is actually listening on that port, so a completed handshake proves the port is open. A refusal means closed; silence usually means a firewall is dropping the packets.
Question 4: Explain the difference between .get() and square-bracket indexing when reading a value from a dictionary. When is .get() the safer choice?
record["key"] raises a KeyError if the key is missing. record.get("key", default) returns the default instead. .get() is the safer choice whenever a field might legitimately be absent, for example an operating-system guess the scanner could not make. Use brackets only when a missing key really means something is wrong.
Question 5: A banner grab against a hardened host returns nothing. Give two different reasons this might happen that do not involve the port being closed.
Any two of: the service waits for the client to speak first, as web servers do, so there is nothing to read; the service has been configured to hide or suppress its banner; a firewall silently drops the connection so the read times out; the timeout was too short for a slow host.
Question 6: Using the registered-post and postcard analogy, state one service that should use TCP and one that should use UDP, and justify each choice.
A file transfer or an SSH login should use TCP (registered post): a single dropped byte would corrupt the result, so delivery must be acknowledged and resent if lost. A DNS lookup or a live video stream should use UDP (postcard): one lost frame or one retried query costs less than the delay of guaranteeing delivery.
Lab exercises
All three exercises run only inside a laboratory you control. The first one builds it.
Exercise 1: Build your laboratory
Objective: create the isolated environment every later exercise needs.
- Install VirtualBox or VMware, and create one Linux virtual machine. In its network settings choose a host-only adapter, not NAT or bridged, so it has no route to the internet or to production systems.
- Inside it, confirm Python 3.10 or later with
python3 --version, then create and activate a virtual environment namedpycyber-env. - Install
requests,dpkt,paramiko,mechanizeandbeautifulsoup4, and record them in arequirements.txtfile.
terminal inside the VM
$ python3 --version
$ python3 -m venv pycyber-env
$ source pycyber-env/bin/activate
(pycyber-env) $ pip install requests dpkt paramiko mechanize beautifulsoup4
(pycyber-env) $ pip freeze > requirements.txt
Try it yourself: Prove the lab is isolated
From inside the VM, try to reach something outside, for example ping -c 2 8.8.8.8. On a properly isolated host-only network it should fail. A lab that can reach the internet is not a lab; fix the network adapter before you go further. (If the install step above needs the internet for pip, install the packages first, then switch the adapter to host-only.)
Exercise 2: TCP echo server and client
Objective: watch both halves of the socket sequence work together.
- Run
tcp_server.py(from the sockets article) in one terminal. - In a second terminal on the same machine, run
echo_client.pypointed at127.0.0.1. - Modify the client to send a short custom message of your own, and confirm the server echoes it back unchanged. Note at which line the three-way handshake actually happens.
Hint: where does the handshake happen?
At sock.connect((HOST, PORT)) on the client, which pairs with server.accept() on the server. By the time connect() returns, SYN, SYN-ACK and ACK have all been exchanged. Nothing is sent until the handshake is complete, so sendall() is the first line that moves your own data.
Exercise 3: banner grabber on a lab host
Objective: read a service's signboard, and see what a hardened host reveals.
- Start an SSH service on a virtual machine inside your lab. (If you prefer, run
lab_banners.pyfrom the banner-grabbing article, which gives you three pretend services on your own machine.) - Run
grab_banner()against it on port 22 (or 9991 for the pretend one) and record the banner. - Repeat against a closed port on the same host, and explain the difference in the function's behaviour between a closed port, which refuses at once, and a filtered port, which never replies at all.
Hint: what should you observe in step 3?
Against a closed port the host answers immediately with a refusal, so Python raises ConnectionRefusedError and grab_banner() returns None quickly. Against a filtered port nothing comes back, so the call waits for the full timeout before raising socket.timeout, and then returns None. Same None, very different waiting time. (On Windows a closed port can take about two seconds to refuse, so use a timeout of three seconds or more when you compare.)
Caution: Authorised systems only
Run every step of this exercise only against a host inside your own lab network. Pointing a banner grabber at a system you do not own is unauthorised access.
Capstone: one script, every idea
Now use everything at once. This script reads a list of lab targets from a file, surveys five ports on each for banners, skips any host whose name does not resolve, and saves the results as JSON. Every article in this unit contributed a piece.
lab_targets.txt
127.0.0.1
no-such-host.invalid
localhost
banner_survey.py
# banner_survey.py: Unit 1 capstone, every idea in one script
import json
import socket
PORTS = [9991, 9992, 9993, 9994, 9995]
def grab_banner(host, port, timeout=1.0):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
try:
sock.connect((host, port))
return sock.recv(1024).decode(errors="replace").strip() or None
except (socket.timeout, ConnectionRefusedError):
return None
finally:
sock.close()
def survey(host):
record = {"host": host, "banners": {}}
for port in PORTS:
banner = grab_banner(host, port)
if banner:
record["banners"][port] = banner
return record
def main():
with open("lab_targets.txt") as f:
targets = [line.strip() for line in f if line.strip()]
results = []
for host in targets:
try:
results.append(survey(host))
except socket.gaierror:
print(f"{host}: name did not resolve, skipped")
with open("survey.json", "w") as f:
json.dump(results, f, indent=2)
for r in results:
print(f"{r['host']}: {len(r['banners'])} banner(s)")
for port, banner in r["banners"].items():
print(f" {port}: {banner}")
if __name__ == "__main__":
main()
Start lab_banners.py in one terminal, then run the survey in another:
Sample output
no-such-host.invalid: name did not resolve, skipped
127.0.0.1: 3 banner(s)
9991: SSH-2.0-OpenSSH_7.4
9992: 220 ProFTPD 1.3.5 Server ready.
9993: 220 mail.lab.local ESMTP Postfix
localhost: 3 banner(s)
9991: SSH-2.0-OpenSSH_7.4
9992: 220 ProFTPD 1.3.5 Server ready.
9993: 220 mail.lab.local ESMTP Postfix
| Idea from this unit | Where it appears in the script |
|---|---|
| Lists and strings | [line.strip() for line in f if line.strip()] cleans the target list |
| Dictionaries | record = {"host": host, "banners": {}} holds each host's result |
| Functions | grab_banner, survey and main each do one job |
| Loops and conditions | for host in targets, for port in PORTS, if banner: |
| File handling | with open(...) to read targets and write survey.json |
| Modules | import json, import socket, and the __name__ guard |
| Sockets | socket.socket, connect, recv, close inside grab_banner |
| Exception handling | except (socket.timeout, ConnectionRefusedError) and except socket.gaierror |
Try it yourself: Make it yours
Extend the capstone in three small steps: (1) add the parse_ssh_banner() function from the banner-grabbing article and print the software and version for any SSH banner; (2) add a timestamp to each record using datetime; (3) write a second JSON file that lists only hosts with an old version. When your script can do all three without help, you are ready for Unit 2.
Key takeaway: Key takeaway
You now have the Python you need and the networking you need, joined by the socket API. In Unit 2 you will turn is_port_open() into a full port scanner, automate SSH across a fleet of hosts, and meet the tools an attacker and a defender both use, always inside an authorised lab.

