Python › Module 2 › Lesson 1
Socket Programming
Create TCP connections with Python sockets and understand host, port, and timeouts
Opening
A TCP connect is one question: is anyone on this host listening on this port — not a license to knock on the whole internet.
When a beginner says “check if port 22 is open,” they mean: attempt a TCP handshake and see whether something accepts. Python’s socket module is that attempt in a few lines. The handshake is not magic, not a password dump, and not a stealth exploit. It is the same kind of “hello, are you there?” your browser does before HTTP, except you are looking at the yes-or-no instead of fetching a page. This lesson stays on 127.0.0.1 (localhost) or a lab VM you built and control. You will learn host + port + timeout as the three knobs, why connect() can hang without a timeout, why connect_ex() is cleaner for a yes/no check, and how bind (server) differs from connect (client). The demo probes port 1 or a high unused port on YOUR loopback and expects closed. You will not nmap a café, an ISP, a school, or a random public host. Next — HTTP Requests — uses the same ethics on GET/HEAD to example.com or a server you run.
1. TCP connect is “is anyone listening” — a three-way handshake from your vantage point
TCP is a connection-oriented transport. A client that calls connect() on an IPv4 TCP socket (AF_INET + SOCK_STREAM) sends a SYN to host:port. If a process is bound and listening, the stack answers SYN-ACK and the client finishes ACK — the socket is then connected. If nothing is listening, the typical lab result on localhost is an immediate refusal (connection refused). If a firewall drops the probe, you may wait until timeout with no RST — that looks like “filtered” from here, not like a guaranteed empty port. Success means: from this process, on this machine, that destination accepted a TCP connection. It does not mean the service is safe, the banner is honest, or the host has no firewall. It does not mean you may probe the next address on the LAN.
Think of host:port as a labeled door on one building. 127.0.0.1 is the building you are standing inside — your own kernel’s loopback. Port 22 is a door number often used by SSH; port 80 by HTTP; port 1 is almost never a listener on a laptop. Walking down every door on someone else’s street is not “practice.” Walking up to door 1 on your own house and confirming it is locked is how you learn what refused versus timeout feels like. Cyberlium’s rule for this whole module: sockets and scanners aim at 127.0.0.1, localhost, or a VM IP you assigned to a guest you own. Never café Wi-Fi clients, never the ISP’s gear, never a school /24, never a random internet host “because Python can.”
2. Three knobs: host, port, timeout — miss one and the script lies or hangs
Host is the destination address. In this course the host string is a constant: "127.0.0.1". Localhost as a name usually resolves to the same place; we hardcode the IPv4 loopback so you cannot accidentally pass sys.argv[1] as the café gateway. Port is a 16-bit number 1–65535 identifying a listener. Timeouts are seconds (floats are fine): s.settimeout(1.0) means “wait at most one second for connect to finish.” Without a timeout, a filtered port or a path that blackholes SYNs can block the thread until the OS gives up — often tens of seconds, sometimes until you kill the process. A teaching scanner with no timeout looks frozen. A teaching scanner with timeout=1 fails closed: you get an error, you print closed_or_filtered, you move on.
Small ranges matter as much as the host. Checking port 1 and port 59999 on loopback is a lesson. Checking ports 1–65535 on a neighbor’s printer is an attack. Checking 22, 80, 443 on a company you do not have in writing is still unauthorized access even if you “only used connect().” The API does not grant consent. Timeout plus a tiny list plus a hardcoded loopback is how beginners stay on the right side of the line while they learn errno versus success.
3. connect() raises; connect_ex() returns 0 or an errno — scanners want the number
s.connect((host, port)) either returns None on success or raises OSError (timeout, refused, unreachable). That is correct for a client that must talk to a known service. For a yes/no “is it open?” loop, exception control-flow gets noisy. connect_ex((host, port)) returns 0 if the connect succeeded and a platform errno otherwise (for example connection refused, timed out). You still must settimeout first — connect_ex honors the socket timeout. You still must close the socket in a finally block so you do not leak descriptors if you loop. You still must not treat errno as a fingerprinting oracle for remote networks you do not own. On localhost, 0 versus not-0 is enough: open versus not open from here.
Always create the socket, set the timeout, try the connect, then close. A pattern that forgets close() will eventually hit “too many open files” if you later loop even a modest port list. A pattern that forgets timeout will hang on port 1 if a weird local filter drops instead of refusing. A pattern that takes HOST from the clipboard will one day paste a public IP. Hardcode 127.0.0.1 in the file you run for this lesson. Read the return code. Print it. That is the whole skill.
4. Bind vs connect: servers attach a local door; clients knock on a door
bind() + listen() + accept() is the server story: your process claims a local address and port and waits. A common teaching server is python -m http.server 8000 on YOUR machine, which binds 0.0.0.0:8000 or a local interface and accepts HTTP. That is how you later create an OPEN port on loopback for the scanner lab — you start a listener you own. connect() is the client story: you specify someone else’s (or your own) host:port and ask to be accepted. You do not bind a scanner. You do not need root to connect to a high port on localhost. You do not “open” a remote port by connecting; you only observe whether it already accepted you.
Mixing the two in your head causes bad labs. Students sometimes bind 0.0.0.0:22 as “practice SSH” on a shared box and knock everyone else off, or they connect to 192.168.1.1 because “the router is local.” The router on café Wi-Fi is not your lab VM. Loopback 127.0.0.1 never leaves your machine. A guest VM whose IP you set in VirtualBox/VMware/Hyper-V and whose network you control can be in scope if it is yours. The school DHCP pool is not. This lesson’s demo does not start a server yet; it only knocks on a door that should be shut so you feel a clean refusal.
5. Wrong vs right: scanning the LAN vs knocking on your own unused port
Worked failure — same socket API, opposite legality. Right never includes nmap, café Wi-Fi, ISP gear, or school subnets.
Wrong
Change host to 192.168.0.1, the café gateway, a classmate’s laptop, or a random public IP. Sweep 1–65535. Run nmap because “Python is slow.” Skip settimeout and let the script hang, then kill it and try a bigger range. Treat connect success as permission to banner-grab production SSH. Paste a cloud IP from a job posting into HOST. Scan the ISP’s resolvers “to see latency.” Any of those is unauthorized probing. This course does not teach stealth scans, SYN tricks, or masscan.
Right
Keep HOST = "127.0.0.1". Set timeout to about 1 second. Probe port 1 or a high unused port (for example 59999) and expect closed_or_filtered. Use connect_ex, print the errno, close the socket. Optionally later start YOUR own http.server on 8000 so one port is OPEN — still on this machine. Write notes under $HOME/cyberlium-lab with chmod 600. Never store live passwords in those notes. Next lesson GETs example.com or a local server you run — still no login brute force.
6. Practical: probe 127.0.0.1:1 (or a high unused port) — expect closed
Copy the block onto YOUR machine. Do not change HOST. Port 1 is TCP portmux historically and is almost never listening on a laptop; a high port like 59999 is also usually closed unless you bound something there. You want a refused or timed-out result so you can see connect_ex != 0. If you somehow have a listener on port 1 (rare), pick another unused high port rather than moving the host off loopback. Save a one-line result to $HOME/cyberlium-lab if you like; chmod 600. Windows: use python or py -3; chmod via Git Bash/WSL or restrict the notes file in your profile. Do not add a LAN target because the result was “too boring.” Closed on localhost is the correct lesson.
Command guide
Localhost-only TCP connect_ex — port 1 should be closed
LOCALHOST ONLY. Do not change HOST. Do not scan a LAN, café, ISP, or school.
Command — copy this
import socket
Command — copy this
HOST = "127.0.0.1" # hardcoded loopback — never sys.argv, never a pasted public IP PORT = 1 # almost never a listener; try 59999 if you already bound :1 TIMEOUT = 1.0 # hang vs fail: without this, a dropped SYN can freeze the script
Command — copy this
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(TIMEOUT)
try:
err = s.connect_ex((HOST, PORT))0 = connect succeeded (open from here). Non-zero = refused, timed out, or unreachable.
Command — copy this
if err == 0:
print(f"{HOST}:{PORT} OPEN (unexpected on port 1 — pick another unused port)")
else:
print(f"{HOST}:{PORT} closed_or_filtered connect_ex={err}")
finally:
s.close()Optional notes (no secrets):
Optional command
mkdir -p "$HOME/cyberlium-lab" && echo "127.0.0.1:1 closed" >> "$HOME/cyberlium-lab/socket-demo.txt" chmod 600 "$HOME/cyberlium-lab/socket-demo.txt"
NEVER: HOST = "192.168.1.1" or a café / campus / ISP address NEVER: for p in range(1, 65536): on any host you do not own NEVER: nmap, masscan, or SYN-stealth tricks
Mission: one refused connect on 127.0.0.1
1) In your own words, explain TCP connect as “is anyone listening on host:port from my vantage point,” and name the three knobs (host, port, timeout). 2) Run the copy-paste script unchanged (HOST stays 127.0.0.1). Use port 1 or 59999. Record connect_ex != 0 as closed. 3) Write one sentence on bind vs connect, and one sentence on why a missing timeout hangs instead of failing. Optional: save the line to $HOME/cyberlium-lab and chmod 600. Do not scan a LAN, café, ISP, school, or random internet host.
Stuck? Ask Cyberlium AI Mentor
If “connection refused” still feels like a failure you should fix by changing HOST, ask for a hint — not a scan plan. Try: "Hint only: why is connect_ex != 0 the expected result on 127.0.0.1 port 1, why must settimeout be set before connect, and why is bind different from connect?" You still run the script on loopback. No nmap, no café Wi-Fi, no public IPs.
You now treat a socket as host + port + protocol, TCP connect as a listening check from your vantage point, and timeout as the difference between a clean fail and a hung process. connect_ex returns 0 or an errno; bind is the server’s job. The only legal classroom target is 127.0.0.1, localhost, or a VM you control. Next — HTTP Requests (requests library) — GET and HEAD to example.com or a server you run, with timeout=, reading status and headers — never a login brute-force.
Knowledge Check
APPLY: You run connect_ex(("127.0.0.1", 1)) with settimeout(1.0) and get a non-zero errno. A classmate says “change HOST to the café gateway so something is open.” What did the call mean, and what do you do?
Multiple choice
Knowledge Check
APPLY: True or False: Leaving out s.settimeout(...) is fine because connect() always returns immediately, and bind() is just another name for connect() on TCP.
True or False
Knowledge Check
APPLY: Why prefer connect_ex for a yes/no open check on YOUR loopback, and which host belongs in the script?
Multiple choice