Skip to content

Latest commit

 

History

History
790 lines (589 loc) · 17.9 KB

File metadata and controls

790 lines (589 loc) · 17.9 KB

Lab 4.1: Python Socket Programming Basics

Overview

Sockets are the foundation of all network communication. HTTP, SSH, DNS, and every other network protocol uses sockets under the hood. In this lab, you'll learn how sockets work by building simple TCP and UDP servers and clients.

What you'll learn:

  • What sockets are and how they work
  • TCP vs UDP differences
  • Building basic TCP server/client
  • Building basic UDP server/client
  • How HTTP actually works (spoiler: it's just TCP!)
  • Foundation for building REST APIs

Part 1: What is a Socket?

A socket is an endpoint for network communication. Think of it as a phone connection:

IP Address = Phone Number
Port = Extension Number
Socket = The actual phone call

Every network connection has:

  • Source IP + Source Port
  • Destination IP + Destination Port

Example:

Your browser: 192.168.1.100:54321 → Google: 142.250.80.46:443

Common Ports

Port Protocol Description
22 SSH Secure shell
53 DNS Domain name lookup
80 HTTP Web traffic
443 HTTPS Encrypted web
514 Syslog Logging

TCP vs UDP

Feature TCP UDP
Connection Required (handshake) Connectionless
Reliability Guaranteed delivery No guarantee
Order Packets in order May arrive out of order
Speed Slower (overhead) Faster (no overhead)
Use Cases HTTP, SSH, FTP DNS, Streaming, Syslog

Analogy:

  • TCP = Phone call (connection required, reliable)
  • UDP = Sending a postcard (no connection, might get lost)

Part 2: TCP Basics

TCP requires a connection before data transfer. Server listens, client connects.

Step 1: Simple TCP Server (Persistent)

Create tcp_server.py:

import socket

# Create a TCP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind to address and port
HOST = '127.0.0.1'  # localhost
PORT = 8080

server_socket.bind((HOST, PORT))

# Listen for incoming connections (max 5 in queue)
server_socket.listen(5)
print(f"TCP Server listening on {HOST}:{PORT}")

# Connection counter
connection_count = 0

# Keep server running
while True:
    # Accept a connection (blocks until client connects)
    client_socket, client_address = server_socket.accept()
    connection_count += 1
    print(f"\nConnection #{connection_count} from {client_address}")
    
    # Receive data from client
    data = client_socket.recv(1024)  # Buffer size 1024 bytes
    print(f"Received: {data.decode()}")
    
    # Send response with counter to client
    response = f"Hello! You are connection #{connection_count}"
    client_socket.send(response.encode())
    
    # Close this client connection (but keep server running)
    client_socket.close()

Run it in a screen session:

# Start a screen session
screen -S tcp_server

# Run the server
python tcp_server.py

# Detach from screen: Press Ctrl+A, then D
# Server keeps running in background
# screen -list # shows all screens running 

To reattach later:

screen -r tcp_server

Step 2: Simple TCP Client

Create tcp_client.py:

import socket

# Create a TCP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect to server
HOST = '127.0.0.1'
PORT = 8080

client_socket.connect((HOST, PORT))
print(f"Connected to {HOST}:{PORT}")

# Send message to server
message = "Hello from client!"
client_socket.send(message.encode())

# Receive response from server
data = client_socket.recv(1024)
print(f"Received: {data.decode()}")

# Close connection
client_socket.close()

Run it multiple times:

python tcp_client.py
# Output: Received: Hello! You are connection #1

python tcp_client.py
# Output: Received: Hello! You are connection #2

python tcp_client.py
# Output: Received: Hello! You are connection #3

Watch the server output:

# Reattach to see the counter incrementing
screen -r tcp_server

Cleanup when done:

# Reattach to the screen session
screen -r tcp_server

# Stop the server: Press Ctrl+C

# Exit the screen session: type 'exit' or press Ctrl+D

# Or kill the screen session from outside:
screen -X -S tcp_server quit

Step 3: Understanding the Flow

TCP SERVER                          TCP CLIENT
-----------                         -----------
1. socket()                         
2. bind(HOST, PORT)                 
3. listen()                         
4. accept() [WAITING...]            1. socket()
                                    2. connect(HOST, PORT)
5. [CONNECTION ESTABLISHED]         3. [CONNECTION ESTABLISHED]
6. recv() ← "Hello from client!"   4. send("Hello from client!")
7. send("Hello from server!") →     5. recv() ← "Hello from server!"
8. close()                          6. close()

Key points:

  • socket.AF_INET = IPv4
  • socket.SOCK_STREAM = TCP
  • Server uses bind(), listen(), accept()
  • Client uses connect()
  • Both use send() and recv()
  • Always close sockets!

Part 3: UDP Basics

UDP has no connection. Just send and receive messages.

Step 1: Simple UDP Server (Persistent)

Create udp_server.py:

import socket

# Create a UDP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Bind to address and port
HOST = '127.0.0.1'
PORT = 9090

server_socket.bind((HOST, PORT))
print(f"UDP Server listening on {HOST}:{PORT}")

# Message counter
message_count = 0

# Keep server running
while True:
    # Receive data (no accept() needed - UDP is connectionless!)
    data, client_address = server_socket.recvfrom(1024)
    message_count += 1
    print(f"\nMessage #{message_count} from {client_address}: {data.decode()}")
    
    # Send response back to client
    response = f"Received! Message #{message_count}"
    server_socket.sendto(response.encode(), client_address)

Run it in a screen session:

# Start a screen session
screen -S udp_server

# Run the server
python udp_server.py

# Detach from screen: Press Ctrl+A, then D
# Server keeps running in background

To reattach later:

screen -r udp_server

Step 2: Simple UDP Client

Create udp_client.py:

import socket

# Create a UDP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Server address
HOST = '127.0.0.1'
PORT = 9090

# Send message (no connect() needed!)
message = "Hello from UDP client!"
client_socket.sendto(message.encode(), (HOST, PORT))
print(f"Sent to {HOST}:{PORT}")

# Receive response
data, server_address = client_socket.recvfrom(1024)
print(f"Received from {server_address}: {data.decode()}")

# Close socket
client_socket.close()

Run it multiple times:

python udp_client.py
# Output: Received from ...: Received! Message #1

python udp_client.py
# Output: Received from ...: Received! Message #2

python udp_client.py
# Output: Received from ...: Received! Message #3

Watch the server output:

# Reattach to see the counter incrementing
screen -r udp_server

Cleanup when done:

# Reattach to the screen session
screen -r udp_server

# Stop the server: Press Ctrl+C

# Exit the screen session: type 'exit' or press Ctrl+D

# Or kill the screen session from outside:
screen -X -S udp_server quit

Step 3: TCP vs UDP Code Comparison

TCP:

# Server
server.bind((HOST, PORT))
server.listen()
client, addr = server.accept()  # Wait for connection
data = client.recv(1024)
client.send(response)

# Client
client.connect((HOST, PORT))    # Establish connection
client.send(message)
data = client.recv(1024)

UDP:

# Server
server.bind((HOST, PORT))
data, addr = server.recvfrom(1024)  # No connection
server.sendto(response, addr)

# Client
client.sendto(message, (HOST, PORT))  # No connection
data, addr = client.recvfrom(1024)

Key differences:

  • UDP uses sendto() / recvfrom() (includes address)
  • TCP uses send() / recv() (connection already established)
  • UDP has no connect(), accept(), or listen()

Part 4: How HTTP Actually Works

HTTP is just text over TCP on port 80 (or 443 for HTTPS).

Step 1: Setup - Install Apache Web Server

First, install and start Apache (httpd):

# Install Apache
sudo dnf install httpd -y

# Start Apache service
sudo systemctl start httpd

# Verify it's running
sudo systemctl status httpd

# Apache is now serving content on port 80

Step 2: Manual HTTP Request (Building curl from scratch!)

Create http_manual_client.py:

import socket

# Create TCP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect to local Apache server
HOST = 'localhost'
PORT = 80

client_socket.connect((HOST, PORT))
print(f"Connected to {HOST}:{PORT}")

# Send raw HTTP request
http_request = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
client_socket.send(http_request.encode())
print("Sent HTTP request:")
print(http_request)

# Receive HTTP response
response = b""
while True:
    chunk = client_socket.recv(4096)
    if not chunk:
        break
    response += chunk

client_socket.close()

# Print response
print("\nReceived HTTP response:")
print(response.decode())

Run it:

python http_manual_client.py

You'll see:

HTTP/1.1 200 OK
Date: Mon, 16 Dec 2025 10:30:00 GMT
Server: Apache/2.4.x
Content-Type: text/html
...

<!doctype html>
<html>
...

Note: You might see "Apache Test Page", "It works!", or even a 403 Forbidden response. All are normal - it shows Apache is running and responding with real HTTP. A 403 just means directory listing is disabled (security feature).

Step 3: Compare with curl

This is exactly what curl does!

# Run curl to see the same thing
curl -v http://localhost/

# curl is just a fancy HTTP client built on sockets!

What you just built:

  • ✅ You created your own curl
  • ✅ Raw TCP socket connection
  • ✅ Manual HTTP request formatting
  • ✅ Response parsing

curl does the same thing - it's just HTTP over TCP sockets with better formatting and features!

Step 4: Cleanup - Stop Apache

Before building our own HTTP server, stop Apache to free up port 80:

# Stop Apache service
sudo systemctl stop httpd

# Verify it's stopped
sudo systemctl status httpd

Step 5: Build Your Own HTTP Server (Persistent)

Note: Make sure your security group has port 8000 open if testing from outside your VM!

Create http_simple_server.py:

import socket

# Create TCP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

HOST = '0.0.0.0'  # Listen on all interfaces
PORT = 8000

server_socket.bind((HOST, PORT))
server_socket.listen(5)
print(f"HTTP Server running on http://{HOST}:{PORT}")
print("Open your browser and visit the URL above")
print("Press Ctrl+C to stop")

# Request counter
request_count = 0

# Keep server running
while True:
    # Accept connection
    client_socket, client_address = server_socket.accept()
    request_count += 1
    print(f"\nRequest #{request_count} from {client_address}")
    
    # Receive HTTP request
    request = client_socket.recv(1024).decode()
    print("Request received:")
    print(request.split('\r\n')[0])  # Print just the request line
    
    # Send HTTP response
    http_response = f"""HTTP/1.1 200 OK
Content-Type: text/html
Connection: close

<!DOCTYPE html>
<html>
<head><title>Socket Server</title></head>
<body>
    <h1>Hello from a Socket!</h1>
    <p>This is raw HTTP over TCP.</p>
    <p>No Flask, no http.server - just sockets!</p>
    <p><strong>You are visitor #{request_count}</strong></p>
</body>
</html>
"""
    
    client_socket.send(http_response.encode())
    client_socket.close()

Run it in a screen session:

# Start a screen session
screen -S http_server

# Run the server
python http_simple_server.py

# Detach from screen: Press Ctrl+A, then D

Test it:

# From command line
curl http://localhost:8000

# Or open in browser (multiple times to see counter increment)
# http://<your-vm-ip>:8000

Watch the server count requests:

screen -r http_server

Cleanup when done:

# Reattach and stop
screen -r http_server
# Press Ctrl+C to stop server
# Type 'exit' to close screen

# Or kill from outside
screen -X -S http_server quit

Step 3: Understanding HTTP

HTTP is a text protocol over TCP:

CLIENT SENDS:
-------------
GET /api/data HTTP/1.1
Host: example.com
User-Agent: Python
Accept: application/json

[blank line]

SERVER RESPONDS:
----------------
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 27

{"status": "success"}

Key parts:

  1. Request line: GET /api/data HTTP/1.1
  2. Headers: Host: example.com
  3. Blank line: \r\n\r\n (separates headers from body)
  4. Body: (optional) JSON, HTML, etc.

This is what requests library does for you! This is what Flask/http.server does for you!


Part 5: Handling Errors

Sockets can fail. Always handle errors.

import socket

def safe_tcp_client(host, port):
    """TCP client with error handling"""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)  # 5 second timeout
        
        sock.connect((host, port))
        sock.send(b"Hello")
        
        data = sock.recv(1024)
        print(f"Received: {data.decode()}")
        
        sock.close()
        
    except socket.timeout:
        print("Connection timed out")
    except ConnectionRefusedError:
        print("Connection refused - is server running?")
    except socket.gaierror:
        print("Invalid hostname")
    except Exception as e:
        print(f"Error: {e}")

# Test it
safe_tcp_client("127.0.0.1", 8080)

Common errors:

  • ConnectionRefusedError - Server not running
  • socket.timeout - Server not responding
  • socket.gaierror - Bad hostname/IP

Part 6: Challenge – Build a Real HTTP Server (Request Parsing)

For this challenge, you will build a basic HTTP server from scratch using Python sockets. Your server must:

  • Accept HTTP requests and parse the request line and headers.
  • Respond with a simple HTML page that displays:
    • The HTTP method (GET, POST, etc.)
    • The path/page requested (e.g., /hello)
    • The browser/user-agent string
  • Only respond with HTTP 200 OK for a few specific endpoints (e.g., /, /hello, /about).
  • For all other paths, return an HTTP 404 Not Found with a simple HTML error page.

Example HTTP Request

GET /hello HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0
Accept: text/html

Example HTML Response (for /hello)

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

<html>
<body>
    <h1>Request Info</h1>
    <p><strong>Method:</strong> GET</p>
    <p><strong>Path:</strong> /hello</p>
    <p><strong>Browser:</strong> Mozilla/5.0</p>
</body>
</html>

Example HTML Response (for unknown path)

HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8

<html>
<body>
    <h1>404 Not Found</h1>
    <p>The page you requested does not exist.</p>
</body>
</html>

Requirements

  • Use only the Python standard library (socket, etc.).
  • Parse the request line and headers manually.
  • Extract the method, path, and User-Agent.
  • Respond with the correct HTML and status code.
  • Server should handle multiple requests (loop).

Tip: You can test your server with a browser or with curl.


Success Criteria

You've completed this lab when you can:

  • Explain what a socket is (IP + Port)
  • Understand TCP vs UDP differences
  • Create a TCP server that listens and accepts connections
  • Create a TCP client that connects and sends data
  • Create a UDP server and client
  • Manually send raw HTTP requests over TCP
  • Build a simple HTTP server returning HTML
  • Build a port scanner
  • Handle socket errors properly
  • Complete the configuration server challenge

Key Takeaways

Sockets are the foundation:

  • Every network protocol uses sockets
  • HTTP, SSH, DNS - all built on sockets

TCP (SOCK_STREAM):

  • Connection-oriented (handshake required)
  • Reliable, ordered delivery
  • Server: bind()listen()accept()recv()/send()
  • Client: connect()send()/recv()
  • Used for: HTTP, SSH, FTP, etc.

UDP (SOCK_DGRAM):

  • Connectionless (no handshake)
  • Unreliable, may lose packets
  • Server: bind()recvfrom()/sendto()
  • Client: sendto()/recvfrom()
  • Used for: DNS, Syslog, streaming

HTTP is just TCP:

  • Text-based protocol over TCP port 80/443
  • Request: GET /path HTTP/1.1\r\nHeaders...\r\n\r\nBody
  • Response: HTTP/1.1 200 OK\r\nHeaders...\r\n\r\nBody
  • Libraries like requests and Flask handle this for you

Quick Reference

# TCP Server
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(1)
client, addr = server.accept()
data = client.recv(1024)
client.send(b"response")
client.close()

# TCP Client
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
client.send(b"message")
data = client.recv(1024)
client.close()

# UDP Server
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 9090))
data, addr = server.recvfrom(1024)
server.sendto(b"response", addr)

# UDP Client
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(b"message", ('127.0.0.1', 9090))
data, addr = client.recvfrom(1024)

Additional Resources


Congratulations! You now understand how network communication works at the socket level. This is the foundation for everything in network automation!