Learning Goals
3 minBy the end of this lesson you can:
- Explain what an IP address and a port identify.
- Describe the TCP three-way handshake and how it differs from UDP.
- Name the common ports/protocols (HTTP, HTTPS, SSH, DNS) and their security relevance.
- Inspect connections from Python with the
socketmodule.
Warm-Up · The Postal Analogy
5 minIP address = the building's street address (which machine) port = the apartment number inside it (which program) packet = one envelope of data protocol = the language written inside the envelope (HTTP, etc.)
A connection is identified by four things: source IP + source port, destination IP + destination port. Your code talks to 142.250.x.x:443 (Google on the HTTPS port); the server replies to your IP and port. Understanding this address-and-port model is the foundation for port scanning, firewalls, sockets, and every network defence in this level.
New Concept · IPs, Ports, TCP & UDP
14 minIP addresses
IPv4 192.168.1.10 four 0-255 numbers (≈4.3 billion addresses) IPv6 2001:db8::1 huge address space, hex groups Special: 127.0.0.1 / localhost this machine (your safe lab target) 192.168.x.x / 10.x.x.x private LAN ranges (not routable on the internet) 0.0.0.0 "all interfaces" when a server binds
Ports
A port is a 16-bit number (0-65535) identifying which program on a machine. Servers listen on well-known ports; clients use a random high port.
Well-known ports (security-relevant): 20/21 FTP (plaintext — avoid) 53 DNS 22 SSH (encrypted remote shell) 80 HTTP (plaintext) 25 SMTP (email) 443 HTTPS (encrypted) 3306 MySQL 5432 PostgreSQL 6379 Redis (DBs — should NOT face the internet)
Security relevance: an open port is a door. Port 80 (HTTP) and 21 (FTP) carry data in plaintext — sniffable. A database port exposed to the internet is a classic misconfiguration (Lesson 34). Minimising open ports shrinks your attack surface (Lesson 2).
TCP — reliable, connection-oriented
TCP guarantees delivery, order, and integrity via a connection. It opens with the three-way handshake:
Client Server │ ── SYN ──────────────────────► │ "let's talk?" │ ◄──────────── SYN-ACK ──────── │ "sure, and you?" │ ── ACK ──────────────────────► │ "confirmed" │ ════ connection established ══ │ (now data flows)
HTTP, HTTPS, SSH, and most things use TCP. Port scanners exploit this handshake: a SYN that gets SYN-ACK means the port is open; a refusal means closed (Lesson 19).
UDP — fast, connectionless
UDP just fires packets with no handshake, no delivery guarantee — faster, used for DNS, video calls, games. No connection means different scanning and a different threat profile (easy to spoof the source).
Inspect connections from Python
import socket # resolve a hostname to an IP (a DNS lookup — Lesson 5) ip = socket.gethostbyname("localhost") print("localhost →", ip) # 127.0.0.1 # what's my machine called? print("hostname:", socket.gethostname()) # look up a service's standard port print("https port:", socket.getservbyname("https")) # 443 print("ssh port:", socket.getservbyname("ssh")) # 22
The socket module is your window into the network. We'll build real clients and servers with it in Lessons 7-8; today it's just for inspection.
Worked Example · A Connection Checker (localhost only)
12 minGoal: a tiny tool that checks whether a TCP port is open on a host, by attempting the handshake — run only against 127.0.0.1. This is the atom of a port scanner, taught here against your own machine.
import socket def is_port_open(host: str, port: int, timeout: float = 1.0) -> bool: """True if a TCP connection to host:port succeeds. LOCALHOST ONLY here.""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(timeout) # connect_ex returns 0 on success (the handshake completed) return s.connect_ex((host, port)) == 0 # Only ever your own machine in this lesson: HOST = "127.0.0.1" COMMON = {22: "SSH", 80: "HTTP", 443: "HTTPS", 3000: "dev server", 5432: "PostgreSQL", 6379: "Redis"} print(f"Checking {HOST} (your own machine):") for port, name in COMMON.items(): state = "OPEN" if is_port_open(HOST, port) else "closed" print(f" {port:5} {name:12} {state}")
Checking 127.0.0.1 (your own machine): 22 SSH closed 80 HTTP closed 443 HTTPS closed 3000 dev server OPEN ← e.g. your Next.js dev server 5432 PostgreSQL closed 6379 Redis closed
Read the code
connect_ex attempts the TCP handshake and returns 0 if it completes (port open) — exactly the network behaviour from the concept section, in three lines. We hard-code HOST = "127.0.0.1" because scanning anything else without permission is illegal (Lesson 1). The result is a map of which doors are open on your own machine — useful even defensively: "wait, why is my database port open?" Lesson 19 covers the ethics and Lesson 20 turns this into a full, responsible scanner.
This code could point at any host — which is precisely why we don't. Until Lessons 19-20 establish the ethics and you have a target you own or have written permission for, every port check targets 127.0.0.1. A scanner aimed at a stranger is a crime, not a homework.
Try It Yourself
13 minUse socket.gethostbyname to resolve localhost and your own hostname. Use getservbyname to look up the standard ports for http, https, ssh, and dns (domain).
Start your Next.js dev server (or any local server), then use is_port_open to confirm its port is open on 127.0.0.1 and that a random unused port is closed.
Hint
print("3000 open?", is_port_open("127.0.0.1", 3000)) print("49999 open?", is_port_open("127.0.0.1", 49999)) # almost certainly closed
Build a dict mapping port → (protocol, encrypted?, security note). Write a function that, given a port, prints whether traffic on it is encrypted and one security caution. Include 80, 443, 21, 22, 3306.
Hint
PORTS = { 80: ("HTTP", False, "plaintext — sniffable; prefer 443"), 443: ("HTTPS", True, "encrypted via TLS"), 21: ("FTP", False, "plaintext creds — use SFTP (22) instead"), 22: ("SSH", True, "encrypted; protect with key auth"), 3306:("MySQL", False, "DB port — must NOT face the internet"), } def explain(port): proto, enc, note = PORTS.get(port, ("unknown", None, "research it")) print(f"{port}/{proto}: {'encrypted' if enc else 'PLAINTEXT'} — {note}")
Mini-Challenge · Map Your Own Machine
8 minWrite a script that checks the 20 most common ports on 127.0.0.1 and reports which are open, what service usually runs there, and whether that service is plaintext or encrypted. For any open port, add a one-line note on whether it should be open (defensive thinking). This is a self-audit, the most ethical kind of scan.
Show a sample solution
import socket HOST = "127.0.0.1" SERVICES = { 21: ("FTP", "plaintext"), 22: ("SSH", "encrypted"), 23: ("Telnet", "plaintext — never use"), 25: ("SMTP", "varies"), 53: ("DNS", "varies"), 80: ("HTTP", "plaintext"), 110: ("POP3", "plaintext"), 143: ("IMAP", "plaintext"), 443: ("HTTPS", "encrypted"), 3306: ("MySQL", "plaintext"), 3389: ("RDP", "encrypted"), 5432: ("PostgreSQL", "plaintext"), 6379: ("Redis", "plaintext"), 8000: ("dev", "varies"), 8080: ("HTTP-alt", "plaintext"), 27017: ("MongoDB", "plaintext"), } def is_open(port): with socket.socket() as s: s.settimeout(0.5) return s.connect_ex((HOST, port)) == 0 print(f"Self-audit of {HOST}:") for port, (svc, enc) in sorted(SERVICES.items()): if is_open(port): flag = "⚠️" if "plaintext" in enc else "🔒" print(f" {flag} {port:5} {svc:11} ({enc}) — OPEN: should it be?")
Non-negotiables: localhost only, reports open ports + service + encryption, and a defensive "should it be open?" prompt.
Recap
3 minA connection is identified by source/destination IP + port. An IP picks the machine; a port (0-65535) picks the program on it. TCP is reliable and connection-oriented, opened by the three-way handshake (SYN, SYN-ACK, ACK) — which port scanners use to tell open from closed; UDP is fast and connectionless. Know the security-relevant ports: 80/21 are plaintext, 443/22 are encrypted, and database ports should never face the internet. Python's socket module lets you resolve names and test ports — but only ever against 127.0.0.1 or hosts you're authorised to touch.
Vocabulary Card
- IP address
- Identifies a machine on a network (e.g. 127.0.0.1 = your own).
- port
- A 0-65535 number identifying which program/service to reach.
- TCP handshake
- SYN → SYN-ACK → ACK; establishes a reliable connection.
- TCP vs UDP
- Reliable+ordered vs. fast+connectionless.
Homework
4 minRun your self-audit on your own machine and write up the results: which ports are open, what's listening, and whether each should be. For any plaintext service that's open, note the encrypted alternative. Then write a paragraph explaining the TCP handshake in your own words and why a port scanner relies on it.
Sample · self-audit + handshake explanation
Self-audit of 127.0.0.1:
🔒 3000 dev server OPEN — yes, my Next.js dev server (expected)
⚠️ 5432 PostgreSQL OPEN — local dev DB. Fine on localhost, but it
must NOT bind 0.0.0.0 in prod. Plaintext alt: there's no
"encrypted alt"; the fix is to firewall it / require TLS.
Handshake: the client sends SYN ("can we talk?"); the server
replies SYN-ACK ("yes, can you?"); the client sends ACK ("confirmed")
— now data flows reliably. A scanner sends just the first SYN: a
SYN-ACK back means the port is OPEN (a service is listening); a
reset/refusal means closed. The handshake's response IS the signal.Non-negotiables: localhost audit with a should-it-be-open judgement, and a correct, in-your-own-words handshake explanation.