Python Networking Basics With Practical Projects

Short answer: Python's standard library socket module gives you direct access to TCP and UDP sockets, while higher-level libraries like requests handle HTTP and dnspython covers DNS. You can build a functional port scanner, DNS resolver, and HTTP client with a few dozen lines each.
TL;DR: Networking is the domain where Python scripting earns its keep fastest in IT and security work. The
socketmodule is your foundation; build four short projects — a TCP port checker, a banner grabber, a DNS resolver, and a network reachability scanner — and you'll understand more about how networks work than most people who only use GUI tools.Key Takeaways - Python's built-in
socketmodule handles TCP and UDP connections with no external dependencies. - All network code in this guide is for authorized lab use — practice on systems you own or have written permission to test. - Therequestslibrary wraps HTTP so cleanly that you rarely touch raw sockets for web traffic. -dnspythonis the standard third-party library for structured DNS queries. - Network scripting reinforces your understanding of protocols — you'll understand TCP handshakes better after writing a socket connection than after reading a diagram. - The official Python docs cover the fullsocketmodule API.
Prerequisites

This guide assumes you're comfortable with Python functions and basic control flow. If you need a refresher on functions and classes before working through these projects, see Python functions and classes explained. You should also have a basic understanding of TCP/IP, ports, and DNS — what they do conceptually — before scripting them.
How Python networking works: the socket model

Every network connection in Python, from a simple port check to a full HTTP request, starts with a socket. A socket is an endpoint for sending or receiving data across a network. Python's socket module exposes this directly.
The key parameters when creating a socket:
- Address family:
socket.AF_INETfor IPv4,socket.AF_INET6for IPv6. - Socket type:
socket.SOCK_STREAMfor TCP (connection-oriented),socket.SOCK_DGRAMfor UDP (connectionless).
import socket
# Create a TCP IPv4 socket
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Create a UDP IPv4 socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Most security-relevant networking uses TCP. UDP appears for DNS queries, syslog shipping, and a few other protocols.
TCP connection lifecycle
| Step | What happens | Python call |
|---|---|---|
| Create socket | Allocate a socket object | socket.socket(...) |
| Connect | Three-way handshake with target | sock.connect((host, port)) |
| Send | Push data to the remote end | sock.send(data) |
| Receive | Read data from the remote end | sock.recv(buffer_size) |
| Close | Tear down the connection | sock.close() |
For TCP port checking, you only need steps 1–3. A successful connect() means the port is open and accepting connections.
Network concepts and their Python equivalents

Before the projects, here's a quick mapping of network concepts to Python code.
Project 1: TCP port checker
Goal: Check whether a specific TCP port is open on a target host. This is the foundation of all network scanning tools.
Concepts: Socket creation, connection timeout, error handling.
import socket
def check_tcp_port(host: str, port: int, timeout: float = 2.0) -> tuple[bool, str]:
"""
Check whether a TCP port is open on a host.
Returns a tuple of (is_open, status_message).
Only use this on systems you own or have written permission to test.
"""
try:
with socket.create_connection((host, port), timeout=timeout) as sock:
return True, f"{host}:{port} is open"
except socket.timeout:
return False, f"{host}:{port} timed out"
except ConnectionRefusedError:
return False, f"{host}:{port} is closed (connection refused)"
except OSError as e:
return False, f"{host}:{port} error — {e}"
def scan_common_ports(host: str, ports: list = None) -> list:
"""
Check a list of common ports on a host.
Returns a list of open port numbers.
"""
if ports is None:
ports = [21, 22, 23, 25, 53, 80, 110, 143, 443, 445, 3306, 3389, 8080, 8443]
open_ports = []
for port in ports:
is_open, message = check_tcp_port(host, port)
print(message)
if is_open:
open_ports.append(port)
return open_ports
if __name__ == "__main__":
# Replace with a host you own or are authorized to test
target = "127.0.0.1"
print(f"Scanning {target} for common open ports...\n")
open_ports = scan_common_ports(target)
print(f"\nOpen ports: {open_ports if open_ports else 'none found'}")
What you learn: How TCP connection establishment works, why a refused connection and a timed-out connection are different outcomes, and how to handle network errors gracefully.
Project 2: Banner grabber
Goal: Connect to an open TCP port and read the first bytes the service sends back. Many services (SSH, FTP, SMTP, HTTP) send a banner that identifies the software and version.
Concepts: Sending and receiving data, decoding bytes, timeout handling.
import socket
def grab_banner(host: str, port: int, timeout: float = 3.0, send_probe: bytes = b"\r\n") -> str:
"""
Connect to a TCP port and read the service banner.
Some services require a small probe to trigger a response.
Only use on systems you own or have written permission to test.
"""
try:
with socket.create_connection((host, port), timeout=timeout) as sock:
sock.settimeout(timeout)
# Some services send a banner immediately; others need a prompt
try:
banner = sock.recv(1024)
except socket.timeout:
sock.send(send_probe)
banner = sock.recv(1024)
return banner.decode("utf-8", errors="replace").strip()
except (socket.timeout, ConnectionRefusedError, OSError) as e:
return f"Error: {e}"
if __name__ == "__main__":
targets = [
("127.0.0.1", 22), # SSH
("127.0.0.1", 21), # FTP
("127.0.0.1", 25), # SMTP
]
for host, port in targets:
banner = grab_banner(host, port)
print(f"{host}:{port} → {banner[:120]}")
What you learn: That most network services announce themselves by default. Banner grabbing is the basis of service fingerprinting, and it illustrates why version information in banners is a security concern.
Project 3: DNS resolver
Goal: Look up DNS records for a hostname — A records (IPv4), AAAA records (IPv6), MX records (mail), and TXT records (SPF, DKIM, etc.). DNS enumeration is a foundational recon skill.
Concepts: The socket module's built-in resolver, the dnspython library for structured record types.
import socket
def resolve_hostname(hostname: str) -> dict:
"""
Resolve a hostname to its IP addresses using the system resolver.
Returns a dict with hostname and list of resolved IPs.
"""
result = {"hostname": hostname, "ips": [], "error": None}
try:
addr_infos = socket.getaddrinfo(hostname, None)
# getaddrinfo returns (family, type, proto, canonname, sockaddr)
ips = list({info[4][0] for info in addr_infos})
result["ips"] = sorted(ips)
except socket.gaierror as e:
result["error"] = str(e)
return result
def reverse_lookup(ip: str) -> str:
"""
Perform a reverse DNS lookup (PTR record) for an IP address.
Returns the hostname or an error message.
"""
try:
hostname, _, _ = socket.gethostbyaddr(ip)
return hostname
except socket.herror as e:
return f"No PTR record: {e}"
if __name__ == "__main__":
domains = ["threatblock.io", "python.org", "docs.python.org"]
for domain in domains:
result = resolve_hostname(domain)
if result["error"]:
print(f"{domain}: ERROR — {result['error']}")
else:
print(f"{domain}: {', '.join(result['ips'])}")
# Reverse lookup
print("\nReverse lookups:")
for ip in ["8.8.8.8", "1.1.1.1"]:
print(f"{ip} → {reverse_lookup(ip)}")
What you learn: DNS resolution mechanics, socket-level hostname lookups, forward and reverse DNS queries, and how attackers and defenders use DNS reconnaissance to map infrastructure. These skills apply directly to recon phases in authorized penetration testing and to threat-hunting workflows in SOC environments.
For structured MX, TXT, and CNAME lookups, install dnspython (pip install dnspython) and use dns.resolver.resolve(domain, 'MX'). The built-in socket module only resolves A/AAAA records.
Project 4: Network reachability scanner
Goal: Check whether a list of hosts on a subnet are reachable by probing a common port. This is useful for IT inventory validation and network mapping in authorized lab environments.
Concepts: Combining the port checker, threading for speed, structured output.
import socket
import ipaddress
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
def is_host_reachable(ip: str, port: int = 80, timeout: float = 1.0) -> dict:
"""
Check whether a host is reachable by probing a TCP port.
Returns a dict with ip, reachable flag, and latency.
"""
start = datetime.now()
try:
with socket.create_connection((ip, port), timeout=timeout):
elapsed_ms = (datetime.now() - start).total_seconds() * 1000
return {"ip": ip, "reachable": True, "latency_ms": round(elapsed_ms)}
except (socket.timeout, ConnectionRefusedError, OSError):
return {"ip": ip, "reachable": False, "latency_ms": None}
def scan_subnet(cidr: str, probe_port: int = 80, max_workers: int = 50) -> list:
"""
Scan all hosts in a CIDR subnet for reachability.
Only use on networks you own or are authorized to test.
Returns list of reachable host dicts.
"""
try:
network = ipaddress.ip_network(cidr, strict=False)
except ValueError as e:
print(f"Invalid CIDR: {e}")
return []
hosts = [str(ip) for ip in network.hosts()]
reachable = []
print(f"Scanning {cidr} ({len(hosts)} hosts) on port {probe_port}...")
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(is_host_reachable, ip, probe_port): ip for ip in hosts}
for future in as_completed(futures):
result = future.result()
if result["reachable"]:
reachable.append(result)
print(f" REACHABLE: {result['ip']} ({result['latency_ms']}ms)")
return sorted(reachable, key=lambda x: ipaddress.ip_address(x["ip"]))
if __name__ == "__main__":
# Replace with a subnet you own or are authorized to scan
results = scan_subnet("192.168.1.0/24", probe_port=22)
print(f"\nTotal reachable: {len(results)}")
The max_workers=50 default works well for /24 subnets (254 hosts); reduce it to 10–20 on resource-constrained systems or increase it carefully for larger ranges.
What you learn: Threading for I/O-bound tasks, CIDR notation and the ipaddress module, and how latency varies across a real network. This is also the architecture behind tools like Nmap — parallel probes with timeout-based triage.
Putting the projects together
These four projects represent the building blocks of most network-level security tooling:
| Project | Skill developed | Real-world analogue |
|---|---|---|
| TCP port checker | Service discovery | Nmap's basic port scan |
| Banner grabber | Service fingerprinting | Nmap's version detection (-sV) |
| DNS resolver | Recon and enumeration | dig / nslookup |
| Subnet scanner | Network inventory | Ping sweep + port scan |
For IT professionals who want to move these skills toward automation workflows, the Python automation tasks guide shows how to schedule and operationalize scripts like these. For a security engineering perspective on where these skills fit in day-to-day work, see how Python helps security engineers.
When you're ready to move beyond general-purpose networking into purpose-built offensive tooling for authorized penetration testing labs, that's covered in the offensive Python track at /blog/python-cybersecurity-beginner-to-offensive/.
A note on authorized use
Every script in this guide operates on the network and probes remote hosts. Run them only on systems you own, on lab environments set up for practice, or on systems where you have explicit written authorization. Unauthorized port scanning and connection attempts are illegal in most jurisdictions, regardless of intent. The Python Software Foundation and the broader security community are unambiguous on this: the same techniques used for defense can be misused — use them responsibly.
Frequently asked questions
What is the Python socket module used for?
The socket module is Python's interface to the operating system's network stack. It lets you create TCP and UDP connections, send and receive bytes, and bind to local ports to listen for incoming connections. It's the foundation for nearly all Python network programming — higher-level libraries like requests use sockets internally.
How do I scan a range of ports in Python?
Create a TCP socket with a short timeout, call connect() for each port in the range, and record which ones succeed. The TCP port checker project above shows the complete pattern. For production-quality scanning with OS fingerprinting and service detection, tools like Nmap — which have Python bindings — are more appropriate than raw socket loops.
What is the difference between TCP and UDP in Python networking?
TCP (socket.SOCK_STREAM) is connection-oriented: it establishes a connection before sending data and guarantees delivery and order. UDP (socket.SOCK_DGRAM) is connectionless: packets are sent without establishing a connection, with no delivery guarantee. For most IT and security scripting, you'll use TCP. UDP matters for DNS queries, syslog, and protocols like TFTP or SNMP.
How do I handle timeouts in Python network code?
Pass a timeout argument to socket.create_connection(), or call sock.settimeout(seconds) on an existing socket. When the timeout expires, Python raises socket.timeout. Always catch this exception in network code, along with ConnectionRefusedError and OSError, to handle unresponsive hosts cleanly.
ThreatBlock's Python programming course teaches Python from first principles with a cybersecurity application focus.
About the author: Samaksh is Lead Instructor at ThreatBlock and holds the OSCP, CRTP, CRTO, and CEH certifications, with hands-on experience in penetration testing and red teaming. Read more on the author page · Editorial policy.
