TCP

Transmission Control Protocol

Leonardo Tamiano

Chi sono?

Ciao.

Sono Leonardo Tamiano e attualmente sono un PhD student qui a Tor Vergata.

Lavoro nel laboratorio del prof. Giuseppe Bianchi in svariati contesti:


  • Ricerca in crittografia applicata (TLS)
  • Sviluppo software in reti mobili 5G
  • Penetration testing
  • Insegnamento

    \[(\text{CNS} \rightarrow \text{Computer Network Security})\]

  • Canale didattico/divulgativo su youtube

    https://www.youtube.com/@LT123

Mi è stato chiesto di coprire le seguenti tre lezioni del corso Fondamenti di Internet (FI) tenuto dal professore Nicola Blefari Melazzi:

Giorno Orario Aula
Lunedì 16 gennaio 14:00 B15
Martedì 17 gennaio 11:30 B15
Mercoledì 18 gennaio 14:00 B15

Organizzazione lezioni

Le lezioni che devo coprire sono relative al protocollo

\[\begin{split} \text{TCP} &\longrightarrow \text{Transmission} \\ &\longrightarrow \text{Control} \\ &\longrightarrow \text{Protocol} \\ \end{split}\]

A tale fine utilizzerò principalmente il materiale (slides) del professore Blefari, più qualche esempio pratico mostrato sul mio computer.

Il materiale sarà reso disponibile nei classici canali.

In ogni caso è già disponibile nel seguente link

https://teaching.leonardotamiano.xyz/misc/fi-2022-tcp


Esempi

A seguire riporto tre esempi che fanno vedere piccole applicazioni dei concetti studiati nel modo pratico:

  • Netcat + Wireshark
  • TCP server in C
  • TCP server in Python

Questi esempi non fanno parte del materiale del corso, e sono da considerarsi extra. Per capirli è necessario avere delle basi nelle seguenti aree:

  • linea di comando
  • C
  • python

1 – Netcat + Wireshark

In questo primo esempio mostriamo l'utilizzo di netcat, una famosa utility che permette di instaurare connessioni TCP per leggere e scrivere.

Funzionamento di netcat:

  1. Ci mettiamo in ascolto su un socket TCP/IP
  2. Ci connettiamo al socket in ascolto
  3. Comunicazione two-way

Come prima cosa quindi scegliamo l'interfaccia a cui ci vogliamo connettere tramite il comando ip a

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> ...
   ...
2: vmnet1: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
   ...
3: vmnet8: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
   ...
5: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
    link/ether 4c:d5:77:b9:09:fb brd ff:ff:ff:ff:ff:ff
    inet 192.168.86.21/24 brd 192.168.86.255 scope global ...
       valid_lft 86034sec preferred_lft 75234sec
    inet6 fe80::7f68:a94e:4a64:c817/64 scope link 
       valid_lft forever preferred_lft forever

A questo punto ci possiamo mettere in ascolto

nc -lv -s 192.168.86.21 -p 1337

Con un'altro terminale possiamo poi connetterci alla porta in ascolto e iniziare a comunicare

nc 192.168.86.21 1337

Esempio comunicazione netcat


Se attiviamo wireshark e ci mettiamo in ascolto sull'interfaccia giusta possiamo vedere il flusso di comunicazione dello strato TCP.

Esempio wireshark


2 – TCP Server in C

Come secondo esempio mostriamo l'implementazione di un server TCP che risponde al comando "PING" inviando la stringa "PONG".

Esempio server TCP


Come prima cosa importiamo le librerie di interesse

#include <stdio.h>
#include <errno.h>
#include <strings.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#define BUFFSIZE 1024
#define PORT 1337

const char *COMMAND_STRING = "PING";
const char *OUTPUT_STRING = "PONG\n";

(code/example_2_socket_c_programming/server.c)

Iniziamo il main definendo le principali variabili

int main() {
  int sockfd;
  int connfd;
  int tmp;
  unsigned int len;
  ssize_t msglen;
  char buf[BUFFSIZE];
  struct sockaddr_in serveraddr;
  struct sockaddr_in client;

(code/example_2_socket_c_programming/server.c)

Creiamo il socket e abilitiamo l'opzione SO_REUSEADDR

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) {
    fprintf(stderr, "ERROR: socket() call failed\n");
    return 1;
  }

  // enable socket options
  const int enable = 1;
  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
    fprintf(stderr, "ERROR: setsockopt(SO_REUSEADDR) failed\n");
    return 1;    
  }

(code/example_2_socket_c_programming/server.c)

Effettuiamo il bind del socket con l'indirizzo del server, salvato nella struct serveraddr

  // fill server addr struct
  bzero(&serveraddr, sizeof(serveraddr));         // initialize to 0
  serveraddr.sin_family = AF_INET;
  serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // all interfaces
  serveraddr.sin_port = htons(PORT);

  if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) {
    fprintf(stderr, "ERROR: bind() call failed\n");
    if (errno != 0) {
      fprintf(stderr, "%s\n", strerror(errno));
    }
    return 1;
  }

(code/example_2_socket_c_programming/server.c)

Ci mettiamo in ascolto sul socket creato

  // listen for incoming connections
  printf("Listening on port %d!\n", PORT);
  // backlog set to 1
  if (listen(sockfd, 1) != 0) {
    fprintf(stderr, "ERROR: listen() call failed");
    return 1;    
  }

(code/example_2_socket_c_programming/server.c)

Accettiamo la connessione da parte del client

  len = sizeof(client);
  connfd = accept(sockfd, (struct sockaddr *)&client, &len);
  if (connfd == -1) {
    fprintf(stderr, "ERROR: accept() call failed");
    return 1;
  }

(code/example_2_socket_c_programming/server.c)

Aspettiamo il messaggio del client, controlliamo che sia valido, e inviamo la risposta

  // check if the message is valid
  tmp = strncmp(buf, COMMAND_STRING, strlen(COMMAND_STRING));

  if (tmp != 0) {
    printf("Invalid command sent by the client!\n");
    char *error_msg = "ERROR: Invalid command!\n";
    msglen = strlen(error_msg) + 1;
    strncpy(buf, error_msg, msglen);
  } else {
    printf("OK – Valid command sent by the client!\n");
    printf("OK – About to send correct response\n");
    msglen = strlen(OUTPUT_STRING) + 1;
    strncpy(buf, OUTPUT_STRING, msglen);
  }

  // send message back to the client
  if (send(connfd, buf, msglen, 0) < 0) {
    fprintf(stderr, "ERROR: send() call failed");
    return 1;
  }

(code/example_2_socket_c_programming/server.c)

Chiudiamo i socket e terminiamo il server

  close(connfd);
  close(sockfd);
  
  return 0;
}

(code/example_2_socket_c_programming/server.c)

3 – TCP server in Python

Come terzo ed ultimo esempio, mostriamo come utilizzare un server TCP programmabile in python utilizzando la libreria socktserver.

Esempio server TCP con socketserver


Come prima cosa importiamo le librerie

#!/usr/bin/env python3

import socketserver
import signal
import time

IP_ADDR = "0.0.0.0"
PORT = 4321

(code/code/example_3_python_tcp/server.py)

Poi definiamo la funzione challenge, che verrà chiamata ogni volta che un client si connette al nostro server.

def challenge(req):
    while True:    
        try:
            req.sendall(b'Welcome to basic TCP server.\n'+\
                    b'[1] Option 1.\n' +\
                    b'[2] Option 2.\n' +\
                    b'[3] Option 3.\n' +\
                    b'[4] Option 4.\n')            

            opt = req.recv(4096).decode().strip()

            if opt == "1":
                req.sendall(b"Nice choice!\n")
            elif opt == "2":
                req.sendall(b"Ok, but could be better!\n")
            elif opt == "3":
                req.sendall(b"Not bad :D\n")
            elif opt == "4":
                req.sendall(b"This is it.\n")
            else:
                req.sendall(b"Baaad choice\n")

        except Exception as e:
            print(e)
            req.sendall(b'Invalid Input. Exit!')
            exit(1)

(code/code/example_3_python_tcp/server.py)

Definiamo la classe ReusableTCPServer che eredita dalle classi ForkingMixIn e TCPServer presenti nella libreria python socketserver

class incoming(socketserver.BaseRequestHandler):
    def handle(self):
        challenge(self.request)

class ReusableTCPServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    pass

(code/code/example_3_python_tcp/server.py)

Infine, mettiamo tutto insieme nella funzione main, che chiamiamo non appena lanciamo lo script dalla riga di comando

def main():
    global IP_ADDR, PORT
    socketserver.TCPServer.allow_reuse_address = True
    server = ReusableTCPServer((IP_ADDR, PORT), incoming)
    print(f"Listening on ({IP_ADDR}, {PORT})")    
    server.serve_forever()

if __name__ == "__main__":
    main()

(code/code/example_3_python_tcp/server.py)