Logo
Python Programming

Python Networking Basics With Practical Projects

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 socket module 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 socket module 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. - The requests library wraps HTTP so cleanly that you rarely touch raw sockets for web traffic. - dnspython is 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 full socket module API.

Prerequisites

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

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_INET for IPv4, socket.AF_INET6 for IPv6.
  • Socket type: socket.SOCK_STREAM for TCP (connection-oriented), socket.SOCK_DGRAM for 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

Network concepts and their Python equivalents

Before the projects, here's a quick mapping of network concepts to Python code.

Mapping network concepts to Python libraries and modules A two-column diagram showing network concepts on the left mapped to Python tools on the right. TCP/UDP connections map to socket module. DNS resolution maps to socket.getaddrinfo and dnspython. HTTP/HTTPS maps to requests library. Raw packets map to scapy. TLS/SSL maps to ssl module. Network concept → Python tool Network concept Python tool TCP / UDP connections socket (stdlib) DNS resolution socket.getaddrinfo / dnspython HTTP / HTTPS requests (third-party) Raw packets scapy (advanced, third-party) TLS / SSL ssl (stdlib) Start with socket and requests. Add dnspython and scapy as specific needs arise.
Figure 1. Python network tools by protocol layer. Start with socket and requests; add specialized libraries when needed.

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.

← Back to all articles
{{ toast }}