4. Socket

4.1. socket

../_images/socket-flow.jpg

Figure 54. Socket Flow

4.1.1. Protocols

  • IPv4 - socket.AF_INET

  • IPv6 - socket.AF_INET6

  • UDP - socket.SOCK_DGRAM

  • TCP - socket.SOCK_STREAM

  • Broadcast - socket.SO_BROADCAST

4.1.2. API

  • The bufsize argument of 1024 used above is the maximum amount of data to be received at once

  • accept(), connect(), send(), and recv() are blocking

  • Blocking calls have to wait on system calls (I/O) to complete before they can return a value

Table 78. API

Method

Description

socket()

bind()

listen()

accept()

connect()

connect_ex()

send()

recv()

close()

4.1.3. TCP

4.1.3.1. Server

Listing 469. socket TCP Server
import socket


SERVER_HOST = '127.0.0.1'
SERVER_PORT = 1337


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:

    print(f'Listening on {SERVER_HOST}:{SERVER_PORT}/TCP...')
    sock.bind((SERVER_HOST, SERVER_PORT))
    sock.listen(1)

    while True:
        conn, addr = sock.accept()

        received = conn.recv(1024).decode()
        print(f'From: {addr}, received: "{received}"')

        response = 'Thanks'
        conn.sendall(response.encode())

        if not received:
            print(f'Client {addr} disconnected.')
            conn.close()

4.1.3.2. Client

Listing 470. socket TCP Client
import socket


SERVER_HOST = '127.0.0.1'
SERVER_PORT = 1337


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:

    print(f'Connecting to {SERVER_HOST}:{SERVER_PORT}/TCP')
    sock.connect((SERVER_HOST, SERVER_PORT))

    payload = 'Hello World'
    sock.sendall(payload.encode())

    data = sock.recv(1024).decode()
    print(f'Received: "{data}"')

4.1.4. UDP

4.1.4.1. Server

Listing 471. socket UDP Server
import socket


SERVER_HOST = '127.0.0.1'
SERVER_PORT = 1337


with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:

    print(f'Listening on {SERVER_HOST}:{SERVER_PORT}/UDP...')
    sock.bind((SERVER_HOST, SERVER_PORT))

    while True:
        received, addr = sock.recvfrom(1024)
        received = received.decode()

        print(f'From: {addr}, received: "{received}"')

4.1.4.2. Client

Listing 472. socket UDP Client
import socket


SERVER_HOST = '127.0.0.1'
SERVER_PORT = 1337


with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:

    print(f'Connecting to {SERVER_HOST}:{SERVER_PORT}/UDP')
    sock.connect((SERVER_HOST, SERVER_PORT))

    payload = 'Hello World'
    sock.sendall(payload.encode())

    received = sock.recv(1024).decode()
    print(f'Received: {received}')

4.1.5. Multicast

Listing 473. socket Multicast Client
import socket


def send(data, port=50000, addr='239.192.1.100'):
    """send(data[, port[, addr]]) - multicasts a UDP datagram."""
    # Create the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # Make the socket multicast-aware, and set TTL.
    s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)  # Change TTL (=20) to suit
    # Send the data
    s.sendto(data, (addr, port))


def recv(port=50000, addr="239.192.1.100", buf_size=1024):
    """recv([port[, addr[,buf_size]]]) - waits for a datagram and returns the data."""

    # Create the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # Set some options to make it multicast-friendly
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    try:
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    except AttributeError:
        pass  # Some systems don't support SO_REUSEPORT
    s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 20)
    s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)

    # Bind to the port
    s.bind(('', port))

    # Set some more multicast options
    iface = socket.gethostbyname(socket.gethostname())
    s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(iface))
    s.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(addr) + socket.inet_aton(iface))

    # Receive the data, then unregister multicast receive membership, then close the port
    data, sender_addr = s.recvfrom(buf_size)
    s.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(addr) + socket.inet_aton('0.0.0.0'))
    s.close()
    return data

4.2. socketserver

4.2.1. TCP

4.2.1.1. Server

Listing 474. socketserver TCP Server
from socketserver import BaseRequestHandler, TCPServer


SERVER_HOST = '127.0.0.1'
SERVER_PORT = 1337


class RequestHandler(BaseRequestHandler):
    def handle(self):
        received = self.request.recv(1024).decode()
        print(f'From: {self.client_address}/TCP, received: "{received}"')

        response = 'Thanks'
        self.request.sendall(response.encode())


with TCPServer((SERVER_HOST, SERVER_PORT), RequestHandler) as server:
    print(f'Accepting connections on {SERVER_HOST}:{SERVER_PORT}/TCP...')
    print(f'To stop the server use ``Ctrl-C``\n')
    server.serve_forever()

4.2.1.2. Client

Listing 475. socket TCP Client
import socket


SERVER_HOST = '127.0.0.1'
SERVER_PORT = 1337


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:

    print(f'Connecting to {SERVER_HOST}:{SERVER_PORT}/TCP')
    sock.connect((SERVER_HOST, SERVER_PORT))

    payload = 'Hello World'
    sock.sendall(payload.encode())

    data = sock.recv(1024).decode()
    print(f'Received: "{data}"')

4.2.2. Asynchronous

4.2.2.1. Threaded

  • socketserver.ThreadingTCPServer

  • socketserver.ThreadingUDPServer

Listing 476. socketserver TCP Threaded Client
import socket
import threading
from socketserver import ThreadingTCPServer, BaseRequestHandler


SERVER_HOST = '127.0.0.1'
SERVER_PORT = 1337


class RequestHandler(BaseRequestHandler):
    def handle(self):
        received = self.request.recv(1024).decode()
        print(f'From: {self.client_address}/TCP, received: "{received}"')

        response = 'Thanks'
        self.request.sendto(response.encode(), self.client_address)


def send_message(server_host, server_port, message):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((server_host, server_port))
        sock.sendall(message.encode())
        response = sock.recv(1024).decode()
        print(f'Received: {response}')


with ThreadingTCPServer((SERVER_HOST, SERVER_PORT), RequestHandler) as server:
    # Start a thread with the server -- that thread will then start one more thread for each request
    server_thread = threading.Thread(target=server.serve_forever)

    # Exit the server thread when the main thread terminates
    server_thread.daemon = True
    server_thread.start()
    print(f'Server loop running in thread: {server_thread.name}')

    send_message(SERVER_HOST, SERVER_PORT, 'Hello World 1')
    send_message(SERVER_HOST, SERVER_PORT, 'Hello World 2')
    send_message(SERVER_HOST, SERVER_PORT, 'Hello World 3')

    server.shutdown()

4.2.2.2. Forking

  • socketserver.ForkingTCPServer

  • socketserver.ForkingUDPServer

Listing 477. socketserver TCP Forking Client

4.4. Assignments

4.4.1. Heartbeat

  1. Stwórz klienta i serwer Heart Beat

  2. Zarówno klient jak i serwer ma być uruchamiany w wątkach

  3. Serwer ma przyjmować komunikaty UDP/IPv4 na porcie 1337

  4. Komunikacja ma odbywać się za pomocą protokołu JSON

  5. Klient ma mieć informację o swoim adresie IP i PORT

  6. Klient ma co 5 sekund wysyłać informację do serwera o swoim IP i PORT

  7. Wypisz:

    • datę UTC przyjścia pakietu,

    • IP i PORT przesłany przez klienta.

Hints
  • threading.Timer(frequency: int, fn: Callable).start()

  • socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

  • socketserver.ThreadingUDPServer

4.4.2. Backdoor

  • Complexity level: medium

  • Lines of code to write: 150 lines

  • Estimated time of completion: 75 min

  • Filename: solution/socket_backdoor.py

  1. Stwórz uruchamiany w wątku serwer TCP

  2. Serwer ma być uruchamiany na losowym porcie z przedziału 1025-65535 (dlaczego taki zakres portów?)

  3. Wyciągnij informację o adresie IP i PORT na którym nasłuchuje serwer

  4. Serwer oczekuje na komunikaty w formacie JSON:

    • date: datetime (UTC),

    • command: str,

    • timeout: int.

  5. Serwer wykonuje polecenie zapisane w command w systemie operacyjnym uwzględniając timeout

  6. Prześlij nadawcy JSON z wynikiem wykonania polecenia, tj.:

    • date: datetime (UTC),

    • host: str,

    • port: int,

    • stdout: str,

    • stderr: str,

    • exit_code: int

Hints
  • random.randint()

  • socket.socket(socket.AF_INET, socket.SOCK_STREAM)

  • socketserver.ThreadingTCPServer

  • subprocess.run(cmd: str, timeout: int, shell: bool = True)

  • json.dumps(obj: Any)

  • json.loads(s: str)