Reti di calcolatori - Snippets

Collezione di snippet di codice comunemente utilizzati per reti.

ATTENZIONE

ASSICURATEVI DI COMPRENDERE IL CODICE PRIMA DI UTILIZZARLO

Sockaddr

Gestione di sockaddr_in e sockaddr_in6 per IPv4 e IPv6.

Server IPv4

struct sockaddr_in = server_addr;
// ...
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[1]));
server_addr.sin_addr.s_addr = INADDR_ANY;

Client IPv4

struct sockaddr_in = server_addr;
// ...
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);

Server IPv6

struct sockaddr_in6 = server_addr;
// ...
server_addr.sin6_family = AF_INET6;
server_addr.sin6_port = htons(atoi(argv[1]));
server_addr.sin6_addr = in6addr_any;
// Se si utilizzano ip link local
server_addr.sin6_scope_id = if_nametoindex("enp0s3");

Client IPv6

struct sockaddr_in6 = server_addr;
// ...
server_addr.sin6_family = AF_INET6;
server_addr.sin6_port = htons(atoi(argv[2]));
inet_pton(AF_INET6, argv[1], &server_addr.sin6_addr);
// Se si utilizzano ip link local
server_addr.sin6_scope_id = if_nametoindex("enp0s3");

Socket

Gestione di socket UDP o TCP.

Impostazioni della socket

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
int optval[1] = {1};
size_t optlen = sizeof(optval);
// Abilita il riutilizzo della socket. Evita l'errore "Address already in use"
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, optval, optlen);
// Ottiene lo stato di una impostazione sulla socket.
// Se diversa da 0, l'opzione è abilitata
getsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, optval, &optlen);
if (optval != 0)
    print("SO_REUSEADDR enabled on sockfd %d!\n", sockfd);

Opzioni comuni di setsockopt

OpzioneDescrizioneEsempio di valore
SO_REUSEADDRAbilita il riutilizzo dell'indirizzo locale.int optval[1] = {1}
SO_BROADCASTAbilita il broadcasting sulla socket.int optval[1] = {1}
SO_RCVTIMEOTimeout di ricezione.struct timeval tv = {5, 0}
SO_SNDTIMEOTimeout di invio.struct timeval tv = {5, 0}

Server UDP

struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
// Inizializzazione di server_addr
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
// ...
recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&client_addr, &addr_len);
sendto(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&client_addr, addr_len);
// ...
close(sockfd);

Client UDP

struct sockaddr_in server_addr;
socklen_t addr_len = sizeof(server_addr);
// Inizializzazione di server_addr
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// ...
sendto(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&server_addr, addr_len);
recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&server_addr, &addr_len);
// ...
close(sockfd);

Server TCP

struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
// Inizializzazione di server_addr
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(sockfd, 5);
// ...
int new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
recv(new_sockfd, buffer, sizeof(buffer), 0);
send(new_sockfd, buffer, sizeof(buffer), 0);
// ...
close(new_sockfd);
close(sockfd);

Server TCP con fork

int sockfd, new_sockfd;
// ...
while ((new_sockfd = connect(sockfd, (struct sockaddr *)&client_addr, &len)) > 0)
{
    int pid = fork();
    if (pid == 0) // processo figlio
    {
        close(sockfd);      // chiusura della socket dell'accept
        // TODO: gestione della connessione
        close(new_sockfd);  // chiusura della nuova socket
        exit(EXIT_SUCCESS); // terminazione del processo figlio
    }
    else if (pid > 0) // processo padre
    {
        close(new_sockfd); // chiusura della nuova socket, torna all'accept
        continue;
    }
}

Client TCP

struct sockaddr_in server_addr;
// Inizializzazione di server_addr
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
// ...
send(sockfd, buffer, sizeof(buffer), 0);
recv(sockfd, buffer, sizeof(buffer), 0);
// ...
close(sockfd);

Broadcasting su subnet

struct sockaddr_in broadcast_addr;
size_t addr_len = sizeof(broadcast_addr);
// Indirizzo di broadcast nella subnet 192.168.1.0/24
inet_pton(AF_INET, "192.168.1.255", &broadcast_addr.sin_addr);
//...
// Abilita il broadcasting sulla socket
int optval[1] = {1};
size_t optlen = sizeof(optval);
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, optval, optlen);
//...
sendto(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&broadcast_addr, addr_len);

Broadcasting su rete

struct sockaddr_in broadcast_addr;
size_t addr_len = sizeof(broadcast_addr);
// Indirizzo di broadcast generico: 255.255.255.255
broadcast_addr.sin_addr.s_addr = INADDR_BROADCAST;
//...
// Abilita il broadcasting sulla socket
int optval[1] = {1};
size_t optlen = sizeof(optval);
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, optval, optlen);
// Effettua il binding della socket su una scheda di rete
char netif[1] = "eth0";
setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, &netif, sizeof(netif));
//...
sendto(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&broadcast_addr, addr_len);

Strutture dati ed enumerazioni

Gestione di strutture dati più complesse ed enumerazioni.

Struttura dati

struct Struttura {
    char name[1024];
    int age;
};
// È possibile creare un alias per la struttura
typedef struct Struttura Struttura;
// O direttamente
typedef struct {
    char name[1024];
    int age;
} Struttura;

Enum

enum Elenco {
    CASE_1,
    CASE_2 = 'b',
    CASE_3 = 123123
};
// È possibile creare un alias per l'enum
typedef enum Elenco Elenco;
// O direttamente
typedef enum {
    CASE_1,
    CASE_2,
    CASE_3
} Elenco;

Invio di messaggi tramite struct

Una semplice stringa potrebbe non essere sufficiente per la comunicazione, o necessitare ulteriore parsing.

Per ovviare al problema, si può valutare il vantaggio di inviare un messaggio tramite una struct, che può contenere più informazioni in maniera ordinata.

Strutture dati

typedef enum {
    REGISTER = 'r',
    LOGIN = 'l',
    DELETE = 'd',
    EXIT = 'e',
} Operation;

typedef struct {
    Operation operation; // L'operazione da eseguire è un char nascosto dall'enum
    char username[1024]; // Username
    char password[1024]; // Password
} Message;

Invio del messaggio

Message message;
// Potrei anche leggere un char da stdin con getchar()
message.operation = REGISTER;
strcpy(message.username, "username");
strcpy(message.password, "password");
send(sockfd, &message, sizeof(message), 0);

Ricezione del messaggio

Message message;
recv(sockfd, &message, sizeof(message), 0);
switch (message.operation) { // Si decide cosa fare in base all'operazione
    case REGISTER:
        break;
    case LOGIN:
        break;
    case DELETE:
        break;
    case EXIT:
        break;
}

I/O

Gestione di standard output e input.

Lettura stringa con prompt

char buffer[1024];
printf("Inserisci del testo: "); // Mostra il prompt all'utente
fgets(buffer, sizeof(buffer), stdin);
buffer[strcspn(buffer, "\n")] = '\0'; // Rimuove \n

Lettura di un carattere

char c = getchar();
getchar(); // Rimuove \n dallo standard input

Stampa di una stringa

printf("Hello, world!\n");
// Stampa di stringhe con variabili
char name[1024] = "Mario";
printf("Hello, %s!\n", name);
// Interi, float e double
int age = 24; float height = 1.80; double weight = 70.5;
printf("age: %d, height: %f, weight: %lf\n", age, height, weight);

Stampa su altri stream

// Stampa sullo standard error invece che sullo standard output.
// stderr è un FILE *, e potrebbe essere qualsiasi altro stream, inclusi file.
fprintf(stderr, "Errore!\n");
// Supporta le stesse funzioni di printf
fprintf(stderr, "age: %d, height: %f, weight: %lf\n", age, height, weight);

File

Gestione dei file.

Lettura riga per riga

FILE *fp = fopen("file.txt", "r");
char line[1024];
while (fgets(line, sizeof(line), fp) != NULL) {
    // ...
}
fclose(fp);

Lettura carattere per carattere

FILE *fp = fopen("file.txt", "r");
char c;
while ((c = fgetc(fp)) != EOF) {
    // ...
}
fclose(fp);

Lettura a blocchi

FILE *fp = fopen("file.txt", "r");
char buffer[1024];
while (fread(buffer, sizeof(char), 1024 fp)) {
    // ...
}
fclose(fp);

Scrittura stringa

FILE *fp = fopen("file.txt", "w");
char str[] = "Hello, world!\n";
fputs(str, fp);
fclose(fp);

Scrittura formattata

// "w" sovrascrive l'intero file, usare "a" per append
FILE *fp = fopen("file.txt", "w");.
fprintf(fp, "Hello, world! I am %d years old!\n", 24);
fclose(fp);

Scrittura a blocchi

FILE *fp = fopen("file.txt", "w");
char buffer[1024];
fwrite(buffer, sizeof(char), 1024, fp);
fclose(fp);

Assicurarsi che un file esista

FILE *fp = fopen("file.txt", "a");
if (fp == NULL)
{
    perror("fopen");
    exit(EXIT_FAILURE);
}
fclose(fp);

Rimuovere una riga

FILE *fp = fopen("file.txt", "r"); // file da cui rimuovere la riga
FILE *fp_tmp = fopen("file.tmp", "w"); // file temporaneo
char line[1024];
while (fgets(line, sizeof(line), fp) != NULL) {
    if (/* condizione per rimuovere la riga */) {
        continue;
    }
    fputs(line, fp_tmp);
}
fclose(fp);
fclose(fp_tmp);
remove("file.txt");
rename("file.tmp", "file.txt");

Stringhe

Gestione delle stringhe.

Lunghezza di una stringa

int len = strlen(str);

Tokenizzazione

// La funzione strtok modifica la stringa originale
// aggiungendo \0 al posto del token!
char *token = strtok(str, " ");
while (token != NULL) {
    // Token assumerà il valore di ogni parola della stringa fino a NULL
    token = strtok(NULL, " "); // NULL per continuare sulla stessa stringa
}

Copia di stringhe

strcpy(str, "Hello, world!"); // str = "Hello, world!"
// Copia solo i primi n caratteri
strncpy(str, "Hello, world!", 5); // str = "Hello"

Concatenazione

strcpy(str, "Hello, "); // str = "Hello, "
strcat(str, "world!");  // str = "Hello, world!"

Formattazione di stringhe

sprintf(str, "Hello, %s! I am %d years old!", "world", 24);
// str = "Hello, world! I am 24 years old!"

Terminare la stringa prima di un carattere

char str = "Hello, world!\n";
strchr(str, '\n')[0] = '\0'; // str = "Hello, world!"
// oppure
string[strcspn(str, "\n")] = '\0'; // str = "Hello, world!"
// oppure, per rimuovere l'ultimo carattere
string[strlen(str) - 1] = '\0'; // str = "Hello, world!"

Confronto di stringhe

// Restituisce 0 se le stringhe sono uguali
int result = strcmp(str1, str2);
int result = strncmp(str1, str2, 5); // Confronta i primi 5 caratteri
int result = strcasecmp(str1, str2); // Case insensitive
// Case insensitive e confronta i primi 5 caratteri
int result = strncasecmp(str1, str2, 5);

Conversione da stringa a numero

// Se la conversione fallisce, il valore di n sarà 0
int n = atoi(str);
long n = atol(str);
long long n = atoll(str);

Conversione da numero a stringa

sprintf(str, "%d", n);

Processi

Gestione di processi.

Creazione di un processo

pid_t pid = fork();
if (pid == 0) {
    // Processo figlio
} else if (pid > 0) {
    // Processo padre
} else {
    // Errore
}

Terminazione di un processo

exit(0); // Termina il processo corrente
exit(1); // Termina il processo corrente con errore

Ottenere il proprio PID

pid_t pid = getpid();

Attesa di terminazione di un processo figlio

// Processo padre
wait(NULL); // Aspetta la terminazione di un processo figlio qualsiasi
while (wait(NULL) > 0); // Aspetta la terminazione di tutti i processi figli
waitpid(pid, NULL, 0); // Aspetta la terminazione di un processo figlio con pid

Thread

Gestione di thread.

Compilazione di un programma che utilizza i thread

Per compilare un programma che utilizza i thread, aggiungere l'opzione -pthread al comando gcc.

gcc -pthread -o main main.c

Creazione di un thread

#include <pthread.h>
void* fun(void* arg) {
    // Codice del thread
    printf("%s\n", (char*) arg);
    return NULL;
}
int main() {
    char* arg = "Hello, world!";
    pthread_t thread;
    pthread_create(&thread, NULL, fun, arg);
    pthread_join(thread, NULL);
    return 0;
}

Terminazione di un thread

#include <pthread.h>
void* fun(void* arg) {
    // Codice del thread
    pthread_exit(NULL);
}

Mutua esclusione

#include <pthread.h>
// Variabile globale
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// All'interno del thread
pthread_mutex_lock(&mutex);
// Sezione critica
pthread_mutex_unlock(&mutex);

// Alla fine del programma
pthread_mutex_destroy(&mutex);
return 0;

Semafori

Gestione di semafori.

Creazione di un semaforo

#include <sys/sem.h>
int semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);

Inizializzazione di un semaforo

#include <sys/sem.h>
int semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
int semval = 1; // Valore iniziale del semaforo
semctl(semid, 0, SETVAL, semval);

Operazioni su un semaforo

#include <sys/sem.h>
#define semwait(semid) semop(semid, &(struct sembuf){0, -1, 0}, 1)
#define semsignal(semid) semop(semid, &(struct sembuf){0, 1, 0}, 1)

semwait(semid);
// Sezione critica
semsignal(semid);

Rimozione di un semaforo

#include <sys/sem.h>
semctl(semid, 0, IPC_RMID);

SSH

Gestione di SSH.

Connessione a un server SSH

ssh <user>@<ip>
# Esempio
ssh root@192.168.56.101

Copia di file da locale a remoto

scp <file> <user>@<ip>:<path>
# Esempio, il file server.c viene copiato nella cartella /home/user
scp server.c user@192.168.56.102:
# Esempio, il file client.c viene copiato nella cartella /home/user/compiti
scp client.c root@192.168.56.102:/home/user/compiti

Copia di file da remoto a locale

scp <user>@<ip>:<path> <file>
# Esempio, il file server.c viene copiato
# nella cartella in cui si trova il terminale
scp user@192.168.56.101:server.c .

Forwarding di porte

ssh -L <porta_locale>:localhost:<porta_remota> <user>@<ip>
# Esempio, la porta 8080 in locale 
# viene mappata sulla porta 80 della macchina remota
ssh -L 8080:localhost:80 root@192.168.56.101

Riavvio del servizio SSH

systemctl restart ssh

Connettersi ad una VM con rete "host only"

Per potersi conentere tramite ssh ad una VM, è necessario che questa abbia almeno una scheda di rete impostata in modalità "host only".
Bisogna assicurarsi che la VM sia accesa, il servisio ssh sia attivo e che la scheda di rete sia abilitata con il dhcp.

# Per vedere il nome della scheda di rete
ip a
# nano /etc/network/interfaces
auto <nome_scheda>
iface <nome_scheda> inet dhcp

# Esempio
auto enp0s8
iface enp0s8 inet dhcp

Dopo aver riavviato la macchina, si può accedere alla VM tramite l'indirizzo IP assegnato alla scheda di rete "host only".

Virtualbox script

Script per la creazione e gestione di macchine virtuali con Virtualbox.

Creazione di una macchina virtuale

# Creazione della macchina virtuale da 0
vboxmanage createvm --name "Debian" --ostype "Debian_64" --register
vboxmanage modifyvm "Debian" --memory 1024 --vram 128 --cpus 2
vboxmanage storagectl "Debian" --name "SATA Controller" --add sata --controller IntelAHCI
vboxmanage storageattach "Debian" --storagectl "SATA Controller" --port 0 --device 0 --type hdd --medium "Debian.vdi"
vboxmanage storagectl "Debian" --name "IDE Controller" --add ide
vboxmanage storageattach "Debian" --storagectl "IDE Controller" --port 0 --device 0 --type dvddrive --medium "debian-10.3.0-amd64-netinst.iso"
vboxmanage modifyvm "Debian" --boot1 dvd --boot2 disk --boot3 none --boot4 none
vboxmanage modifyvm "Debian" --nic1 bridged --bridgeadapter1 "Intel(R) Dual Band Wireless-AC 8265"
vboxmanage startvm "Debian"

Clone di una macchina virtuale

# Clone della macchina virtuale. Clone completo.
vboxmanage clonevm "Debian" --name "Debian2" --register
vboxmanage startvm "Debian2"
# Clone della macchina virtuale. Clone linkato.
vboxmanage snapshot "Debian" take "DebianSnapshot" --description "DebianSnapshot"
vboxmanage clonevm "Debian" --snapshot "DebianSnapshot" --name "Debian3" --options link --register
vboxmanage startvm "Debian3"

Gestione di una macchina virtuale

# Avvio della macchina virtuale
vboxmanage startvm "Debian"
# Arresto della macchina virtuale
vboxmanage controlvm "Debian" poweroff
# Sospensione della macchina virtuale
vboxmanage controlvm "Debian" savestate
# Riavvio della macchina virtuale
vboxmanage controlvm "Debian" reset
# Rimozione della macchina virtuale
vboxmanage unregistervm "Debian" --delete
# Rimozione dello snapshot
vboxmanage snapshot "Debian" delete "DebianSnapshot"

Eseguire comandi sulla macchina virtuale

# Esecuzione di un comando sulla macchina virtuale
vboxmanage guestcontrol "Debian" run --exe "/bin/ls" --username "root" --password "root" --wait-stdout --wait-stderr -- ls -l .
# Copia di un file sulla macchina virtuale (guest additions)
vboxmanage guestcontrol "Debian" copyto --username "root" --password "root" "host/file.txt" "/vm/file.txt"
# Copia di un file dalla macchina virtuale (guest additions)
vboxmanage guestcontrol "Debian" copyfrom --username "root" --password "root" "/vm/file.txt" "host/file.txt"

Gestione delle interfacce di rete

# Creazione di una nuova interfaccia di rete
vboxmanage modifyvm "Debian" --nic1 intnet --intnet1 lan1 --nic2 intnet --intnet2 lan2
# Mettere up una interfaccia di rete
vboxmanage guestcontrol "Debian" run --exe /sbin/ip --username "root" --password "root" --wait-stdout -- ip link set enp0s8 up
# Impostare un indirizzo ip su una interfaccia di rete
vboxmanage guestcontrol "Debian" run --exe /sbin/ip  --username root --password root --wait-stdout -- ip addr add 192.168.1.254/24 dev enp0s8
# Abilitare il forwarding su una macchina virtuale
vboxmanage guestcontrol "Debian" run --exe /usr/sbin/sysctl --username "root" --password "root" --wait-stdout -- sysctl -w net.ipv4.ip_forward=1

Varie ed eventuali

Alcuni snippet vari un po' più avanzati che potrebbero tornare utili.

Ordinare un array di interi

#include <stdlib.h>

int compare(const void *a, const void *b)
{
    return *(const int *)a - *(const int *)b;
}

int main()
{
    int array[5] = {3, 1, 0, 2, 4};
    qsort(array, 5, sizeof(int), compare);
    // qsort(array, sizeof(array) / sizeof(*array), sizeof(*array), compare);
}

Ordinare un array di stringhe

#include <stdlib.h>
#include <string.h>

int compare(const void *a, const void *b)
{
    return strcmp(*(const char **)a, *(const char **)b);
}

int main()
{
    char *array[6] = {"ciao", "come", "stai", "amico" "mio" "caro"};
    qsort(array, 6, sizeof(char*), compare);
    // qsort(array, sizeof(array) / sizeof(*array), sizeof(*array), compare);
}

Signup con username univoco

Ogni riga contiene un username, un ip e una porta.
L'username deve essere univoco.

# database.txt
user1 192.168.1.2 3000
int signup(const char username[], const char ip[], int port)
{
    char line[128];
    FILE *db = fopen("database.txt", "r+"); // Controllo che db != NULL
    while (fgets(line, sizeof(line), db))
        if (strcmp(username, strtok(line, " ")) == 0) // Trovato conflitto
        {
            fclose(db);
            return 0; // Username già presente, operazione fallita
        }
    fprintf(db, "%s %s %d\n", username, ip, port); // Registrazione utente
    fclose(db);
    return 1; // Utente registrato con successo
}

Controllare se un ip appartiene alla subnet di una delle interfacce di rete

#include <ifaddrs.h>
int is_in_same_lan(char ip[]){
    struct ifaddrs *ifaddr, *ifa;
    getifaddrs(&ifaddr); // Controllare che l'output non sia -1!
    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET)
            continue;
        int mask = ((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr.s_addr;
        int ip_server = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
        if ((ip & mask) == (ip_server & mask)) {
            freeifaddrs(ifaddr);
            return 1;
        }
    }
    freeifaddrs(ifaddr);
    return 0;
}

Macro per debug

#ifdef DEBUG
#include <error.h>
#include <errno.h>
#define debug(fun)                                                              \
    do                                                                          \
    {                                                                           \
        int __RES__ = fun;                                                      \
        if (__RES__ < 0)                                                        \
            error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__, "%s", #fun); \
    } while (0)
#else
#define debug(fun) fun
#endif

Utilizzo

Permette di wrappare qualsiasi espressione one-liner o funzioni. Se il valore di ritorno dell'espressione è minore di zero, viene interpretato come un errore e fa terminare il programma.
In questo caso saranno fornite informazioni aggiuntive su cosa è andato storto.

// Si può provare il suo funzionamento con una chiamata a socket errata
debug(socket(AF_INET, SOCK_STREAM, IPPROTO_UDP));

Per abilitare questa funzionalità, compilare con -DDEBUG.

gcc -DDEBUG -o main main.c

Gestione di segnali (ctrl+c)

#include <signal.h>
volatile sig_atomic_t is_running = 1;

void signal_handler(int) {
    is_running = 0;
}
int main() {
    signal(SIGINT, signal_handler);
    while (is_running) {
        // ...
    }
    // Cleanup
    return 0;
}