Nozioni di programmazione di rete in C su ambienti UNIX

Bertera Pietro(p.bertera@valtellinux.it )


Indice


Le socket RAW

Introduzione


Questo documento si propone di trattare alcune problematiche relative alla programmazione di rete in C.
In questo testo i suppone che sulle macchine utilizzate sia installato un sistema operativo UNIX o un suo stretto parente e che il software di rete sia TCP/IP.
Agli inizi degli anni ’80, nell’ambito dello sviluppo di un’implementazione di TCP/IP per il sistema UNIX di Berkeley (Unix BSD), è stata defnita una API per l’utilizzo del TCP/IP che è poi diventata uno standard ed è nota come interfaccia SOCKET.
Più di recente è stata costruita un’interfaccia standard anche per Windows chiamata WINSOKET, molto simile all’ interfaccia SOCKET ma non permette facilmente di scender sotto il 4° livello della stratificazione ISO/OSI.
Per questa sua tradizionale censura non verrà trattata nel testo; pertanto le righe di codice scritte saranno codice C per UNIX.

Torna all'indice


Il modello Client-Server


Il software di rete TCP/IP permette a due processi residenti su due macchie diverse di comunicare e trasferire tra di loro dei dati.
Esso infatti fornisce una comunicazione da pari a pari (peer-to-peer) tra i processi.
La maggior parte delle applicazioni distribuite viene costruita secondo un particolare modello di collaborazione detto CLIENT/SERVER.
In questo modo i due processi che cooperano hanno due ruoli differenti nella cooperazione.
Per definizione un processo Server è un processo che offre dei servizi ad altri processi, il Server accetta le richieste che arrivano da altre macchine attraverso la rete, esegue il servizio ed eventualmente fornisce n risultato al richiedente. Il Client è un processo che chiede servizi ad un Server, solitamente si attende una risposta.

Torna all'indice


Indirizzamento


Un processo A che vuole comunicare con un altro processo B deve esser in grado di trovare il suo interlocutore (B).
Ovviamente occorre prima identificare la macchia su cui risiede il processo B, poi il processo stesso all’ interno della macchina.
Nelle reti TCP/IP l’identificazione della macchina avviene tramite un indirizzo IP il quale è un numero a 32 bit assegnato alla macchina e la identifica univocamente nella rete.
Per comodità di utilizzo gli indirizzi IP vengono scritti come quattro cifre che vanno da 0 a 255 separate da un punto (.) ad esempio: 131.175.18.35 ogni campo dell’indirizzo rappresenta un numero binario di otto bit scritto in decimale, tuti e quattro compongono il nostro indirizzo a 32 bit.
Un singolo processo, viene identificato da un Port.
Il Port, viene stabilito dal progettista dell’applicazione distribuita infatti i primi Port da 0 a 1024 sono riservati per servizi particolari ( telnet, ftp, http, smtp, pop, …..).
Bisogna tenere presente che il Port non ha niente a che fare con il PID (Process Identifier) di un processo anche se tutti e due servono ad identificare un processo su di una macchina, le loro differenze sono:


Torna all'indice


La connessione


La comunicazione TCP è orientata alla connessione, ciò significa che i due processi devono stabilire un collegamento prima di poter iniziare a scambiare dati tra di loro.
Nel modello CLIENT/SERVER, per stabilire una connessione occorre che un processo Server sia in attesa di una richiesta di connessione da parte di un processo Client.
I due processi che stabiliscono una connessione tra di loro sono detti punti terminali e sono identificati ciascuno da un indirizzo IP e da un numero di Port.
La connessione stessa è identificata da 2 in dirizzi IP e da due Port.
Una volta stabilita la connessione i due processi hanno a disposizione un canale bidirezionale, sicuro e orientato allo stream tramite cui comunicare.

Torna all'indice


Formato dei dati


Le macchine collegate alla rete che svolgono un lavoro applicativo vengono chiamate host per distinguerle dalla macchine che svolgono le funzioni di gestione di una rete.
Gli host possono essere di diverso tipo e purtroppo ogni tipo rappresenta i dati a suo modo, quindi la stessa sequenza di bit può rappresentare due numeri diversi su due macchine diverse.
Il TCP/IP, per risolvere questo problema definisce un formato di rete e definisce alcune routines di conversione del formato di rete nello specifico formato della macchina host.

Torna all'indice


La connessione: il meccanismo Connect - Accept


Le regole che stabiliscono il meccanismo di creazione di una connessione sono le seguenti:

L’apertura passiva, su di un’interfaccia socket si effettua con la funzione accept(), mentre l’apertura attiva si effettua tramite la connect().

Torna all'indice


Un po' di funzioni:


Il tipico flusso delle chiamate necessarie per la gestione di una connessione socket di tipo TCP è illustrato nella figura seguente





Il significato delle chiamate è il seguente:

Lato server
Lato client

Torna all'indice


I due esempi di programmi che seguono sono due semplici processi che stabiliscono una connessione tra di loro, i parametri passati alle funzioni verranno spiegati in seguito.

Programma Client


Torna all'indice


Programma Server


Torna all'indice


Chiamate principali alle socket di Berkeley

Come già detto, per identificare una connessione occorre che siano definite 5 proprietà: Le funzioni che ci permettono di definire queste proprietà sono descritte di seguito:

Socket

int socket( int famiglia, int tipo, int protocollo ) ;

La funzione socket individua la prima delle proprietà: il protocollo.
Alla funzione vengono passati 3 parametri:

famiglia:
Le famiglie di protocolli utilizzabili dalle socket sono: AF significa ADDRESS FAMILY.

tipo:
il tipo di socket può essere : SOCK_STREAM utilizza uno stream (flusso continuo di byte) per comunicare,
SOCK_DGRAM utilizza dei datagrammi,
SOCK_RAW è un tipo rudimentale perché occorre costruire il pacchetto manualmente.

protocollo:
specifica il protocollo utilizzato dal socket, le principali costanti per i protocolli sono: Le costanti IPPROTO_XXX sono definite nel file <netinet/in.h>
La funzione socket ritorna un intero che identifica il nostro socket, un numero negativo se la chiamata non è andata a buon fine.

Bind

int bind( int sockfd, struct sockaddr *mioindir, int lunghindr) ;

La funzione bind identifica le proprietà indirizzo locale e processo locale della connessione.
La chiamata bind serve a legare il proprio indirizzo alla connessione e tipicamente viene utilizzate in 3 modi:
I parametri passati alla bind sono:
Listen

int listen( int sockfd, int backlog ) ;

Questa funzione è utlizzata da un server per stabilire il massimo numero di conessioni da lasciare in coda.
Solitamente è eseguita dopo le chiamate di sistema socket e bind ed immediatamente prima della chiamata accept.
I parametri sono:
Accept

int accept( int sockfd, struct sockaddr *client_indir, int *lunghindir ) ;

Questa funzione definisce l’indirizzo remoto ed il processo remoto(dal lato server).
Viene eseguita dal server prima dopo la listen e pone il processo in attesa di una connessione da parte di un client.
La accept, prende una connessione che è in testa alla coda specificata dalla listen e crea un’ altro socket (canale privato) con le stesse caratteristiche del socket precedente.
Se non ci sono richieste in sospeso nella coda, la accept blocca il processo chiamante in attesa di una connessione.
I parametri pasati sono: Questa funzione restituisce tre valori:
Connect

int connect( int sockfd, struct sockaddr *indirserver, int lunghindr ) ;

Questa funzione definisce l’indirizzo remoto ed il processo remoto(dal lato client).
Tramite la connect un processo client connette un descrittore di socket nato dalla chiamata di socket, per potere stabilire una connessione con il processo server. Nei protocolli orientati alla connessione (TCP/IP) la connect è l’effettiva attivazione di una connessione e quindi assegna l’indirizzo remoto ed il processo remoto. Se la connect viene utilizzata da un protocollo non orientato alla connessione (UDP) la connect scriverà l’indirizzo del server in indrserver e terminerà immediatamente senza scambiare alcun tipo di dato. I parametri passati sono:
Send, sendto, recv, recvfrom

Descrizione dei parametri
Tutte le funzioni restituiscono la lunghezza dei dati scritti o letti.
Nell'uso tipico di recvfrom con un protocollo senza connessione, il valore di ritorno è la lunghezza del datagramma che è stato ricevuto.

Getsockname

Int getsockname(int fd, struct sockaddr_in *my_addr, int *my_addr_len);

Richiede come parametri il descrittore del socket, un puntatore alla struttura in cui scrivere l’indirizzo ed un puntatore ad un intero che definisce la dimensione dell’indirizzo.
La funzione scrive nella struttura puntata (my_addr) l’IP, la famiglia ed il Port.

Torna all'indice


Routine Importanti

Routine di ordinamento dei byte

Le seguenti 4 funzioni gestiscono le potenziali differenze di ordinamento dei byte tra diverse architteture di computer e di diversi protocolli di rete.Anche se i problemi che sorgono con i sistemi che non impiegano byte di 8 bit stanno lentamente scomparendo , i problemi relativi all'ordinamento dei byte sono ancora presenti : Si consideri un intero di 16 bit ,2 byte ci sono 2 modi per memorizzare questo valore little endian il cui byte meno significativo è all'indirizzo iniziale e big endian in cui il byte piu significativo è all'indirizzo iniziale. legenda:
Torna all'indice


Routine di operazioni di byte:

Il 4.3BSD definisce le seguenti tre routine che operano su stringhe di byte definite dall'utente.
Tali routine sono "definite dall'utente" nel senso che esse non sono stringhe di caratteri standard del C (che sono sempre terminate con un byte nullo). Le stringhe di byte definite dall'utente possono avere byte nulli al loro interno e cio non indica la fine della stringa, bensi è necessario specificare la lunghezza di ciascuna stringa come argomento della funzione.
La prima funzione trasferisce il numero di byte specificato dalla sorgente alla destinazione.
La seconda funzione scrive il numero di byte nulli specificati nella destinazione.
La terza funzione confronta due stringhe di byte arbitrari.

Torna all'indice


Routine di conversione indirizzi:

Le seguenti funzioni operano la conversione tra il formato con un punto decimale ed una struttura in_addr:

unsigned long inet_addr( char *) ;
char *inet_ntoa( struct in_addr inaddr) ;

La prima converte una stringa di caratteri da una notazione con il punto decimale ad un indirizzo di Internet di 32 bit.
La seconda effettua la conversione opposta.

Torna all'indice


Le socket RAW

Cenni di TCP/IP


Schematicamente, l'header di un pacchetto IP e' il seguente (vedi RFC 791):

                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Identification        |Flags|      Fragment Offset    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |         Header Checksum       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Source Address                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Destination Address                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Options                     |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Ogni campo ha una determinata lunghezza in bit come evidenziato nella parte alta della figura.
Il significato dei campi che ci interessano e' il seguente:
Torna all'indice


Introduzione alle Socket RAW

Nei casi più comuni, nelle applicazioni di networking ci si colloca al 4° layer della stratificazione ISO/OSI.
Le socket raw sono un tipo di socket che ci permettono di scendere al di sotto del 4° livello infatti a seconda del parametro protocollo (il terzo) sella syscall socket si puo’ scendere al 3°, al 2° o al 1° layer.

In termini pratici le socket raw ci permettono di Per creare delle socket raw non ci sono particolari problemi: la creazione è prtatcamente identica alla creazione di socket di altrii tipi.

sd = socket(AF_INET, SOCK_RAW, IPPROTO_XXX);

Così facendo verrà creata una socket che utilizzerà il protocollo IPPROTO_XXX.
IPPROTO_XXX è definito in /usr/include/netinet/in.h.
E’ molto interessante la possibilità di creare manualmente l’header IP.
Per poterlo fare occorre impostare l’opzione IP_HDRINCL:

int on=1;
setsockopt(sd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(int));


Ovviamente sd è il descrittore di socket creato prima con il campo parametro protocollo IPPROTO_RAW.
Bisogna fare questa operazione per comunicare al kernel che costruiremo noi l’header IP del pacchetto.

N.B. Le socket Raw possono essere create solo dal superuser!!.

Per inviare dati con le socket raw è possibile utlizzare la sycall sendto() o eventualmente la send() nel caso sia stata precedentemente chamata la connect().
Se è stato settato IP_HDRINCL, nell’ array che verrà spedito dovrà essere presente l’IP header e gli header degli strati superiori (se ce ne sono).

Occorre tenere presente che il Kernel riempie alcuni campi dell’ IP header:
Tabella riassuntiva dei campi dell'IP header modificati dal kernel

IP checksum     SI
Source IP          Si   se riempito con 0
Packet ID          Si   se riempito con 0
Total length       SI

Torna all'indice



Principali strutture utilizzate dalle socket raw

IP Header

In C è presente nella libreria netinet/ip.h una struttura che definisce tutti i campi sopra accennati che verranno chiariti in seguito.

.......
struct iphdr
struct ip_options
.......

Versione
Definisce la versione del protocollo utilizzato (4bit).

Lunghezza dell'header
Questo campo contiene la lunghezza dell’ header in multipli di 32 bit, il minimo è 5 e significa che l’header non ha nessuna opzione ( 20 bytes), il massimo è 15 (header di 60 bytes). (4bit).

Tipo di servizio
Consente di comunicare il tipo di servizio desiderato in base al riardo, alla capacità di trasmissione e alla affidabilità.

Lunghezza del pacchetto
Riguarda l’header IP, gli eventuali header dei livelli superiori (TCP UDP) e i dati, un pacchetto puo’ essere al massimo 65.535 byte (16 bit).

Identificazione
Permette all’ host di destinazione di risalire a quale datagramma appartiene un frammento appena arrivato, tutti i frammenti appartenenti ad un datagramma hanno lo stesso numero di identificazione (16bit).

Offset di frammentazione del pacchetto
Indica la posizione del frammento all’interno del datagramma corrente, tutti i framment tranne l’ultimo devono essere multipli di 8 byte. Il campo è di 13 bit quindi si avrà al massimo 8192 frammenti (65536 byte massimi) (13 bit).

Tempo di vita del pacchetto
E’ un contatore che definisce il massimo tempo di vita di un pacchetto in rete, quando il contatore si azzera il pacchetto muore e viene inviato alla destinazione un messaggio di avvertimento (8 bit).

Protocollo
Campo che indica il tipo di protocollo nello strato superiore, la numerazione dei protocolli è definita nella RFC 1700 (8 bit). L’algoritmo deve essere modificato ad ogni salto in quanto c’è il campo TTL (tempo di vita)che varia volta per volta.

Checksum
Serve a verificare il preambolo IP e serve ad individuare gli errori generati dai router.
L’algoritmo somma tutte le parole di 26 bit utilizzando l’aritmetica del complemento a 1 e quindi prende il complemento a 1 del risultato.
Il risultato deve essere 0 (16 bit).

Indirizzo sorgente e destinatario
Sono gli indirizzi del mittente del pacchetto e del destinatario del pacchetto (32 bit).

OPZIONI

Security
E’ il grado di segretezza delle informazioni, teoricamente è utilizzato dai rouer militari per non fare attraversare paesi a rischio

Strict source routing
Fornisce il percorso completo che deve seguire.

Loose source routing
Richiede di fare passere il pacchetto attraverso specificati router ma non è detto che non passa attraverso altri.

Record route
Forza i ruoter ad aggiungere il loro IP al pacchetto.

Time stamp
Simile alla precedente ma oltre all’IP i router aggiungono anche un timestamp di 32 bit.


Torna all'indice


TCP Header

Struttura header TCP (/usr/include/netinet/tcp.h)

struct tcphdr 
 {u_int16_t source; // porta sorgente 16 bit
  u_int16_t dest; // porta destinazione 16 bit 
  u_int32_t seq; // numero di sequenza 32 bit 
  u_int32_t ack_seq; // numero di ack 32 bit 
  #if __BYTE_ORDER == __LITTLE_ENDIAN 
  u_int16_t res1:4; //  
  u_int16_t doff:4; 
  u_int16_t fin:1; // flag FIN 
  u_int16_t syn:1; // flag SYN 
  u_int16_t rst:1; // flag RST 
  u_int16_t psh:1; // flag PSH 
  u_int16_t ack:1; // flag ACK 
  u_int16_t urg:1; // flag URG 
  u_int16_t res2:2;  
  #elif __BYTE_ORDER == __BIG_ENDIAN 
  u_int16_t doff:4; 
  u_int16_t res1:4; 
  u_int16_t res2:2; 
  u_int16_t urg:1; 
  u_int16_t ack:1; 
  u_int16_t psh:1; 
  u_int16_t rst:1; 
  u_int16_t syn:1; 
  u_int16_t fin:1; 
  #else 
  #error "Adjust your defines" 
  #endif 
  u_int16_t window; 
  u_int16_t check; 
  u_int16_t urg_ptr; 
  };

La struttura precedente rappresenta i vari campi che compongono l'header di un pacchetto TCP il preambolo fisso di 20 byte piu una parte opzionale.
I vari campi sono descritti di seguito:

Sorgente e destinazione
Porta sorgente e porta destinataria che sono gli estremi di una connessione.

Numero di sequenza
Viene asegnato ad ogni pacchetto durante la connessione tra 2 host.

Numero ACK
E’uguale a 1 per indicare che il numero di ack è valido, se vale 0 il numero di ack viene ignorato.

PSH
Indica dei dati di tipo push, così facendo evita che i dati vengano salvati in un buffer di attesa ma vengono inviati direttamente all’applicazione destinataria.

RST
Serve a reinizializzare una connessione instabile a causa di un guasto ad un host o per qualsiasi altro motivo. Si utilizza anche per rifiutare una connessione.

SYN
Si utilizza per realizzare una connessione. La richiesta di connessione ha SYN=1 e ACK=0 la risposta è SYN=1 e ACK=1.

FIN
Viene utilizzato per chiudere una connessione. Dopo aver chiuso la connessione un processo può continuare a ricevere dati.

WINDOW
Il controllo di flusso TCP è gestito utilizzando un protocollo slinding window a dimensione variabile. Il campo window specifica quanti byte possono essere spediti a partire dal byte confermato.

CHECKSUM
Per garantire l'affidabilità è presente anche un campo checksum, che verifica il preambolo i dati e lo pseudopreambolo.
Quando viene eseguito il calcolo, il campo checksum viene posto uguale a 0, e il campo dati viene completato con un 0 se la lunghezza è un numero dispari.
L'algoritmo di checksum somma tutte le parole di 16 bit in complemento a 1 e quindi prende il complemento a 1 della somma.
Come conseguenza quando il ricevente esegue il calcolo sull'intero segmento (compreso il checksum) il risultato deve essere 0.


Torna all'indice


UDP Header

Struttura UDP header (/usr/include/udp.h)
 struct udphdr
  {
  u_int16_t source;
u_int16_t dest;
u_int16_t len;
u_int16_t check;
};
Questa struttura definisce un’intestazione di un pacchetto UDP, differentemente dal TCP, il protocollo UDP non necessita di una connessione ma invia il pacchetto indipendentemente dagli altri.

SORGENTE E DESTINAZIONE
Indirizzi sorgente e destinazione del host mittente e destinatario 32 bit ognuno.

LUNGHEZZA
Lunghezza del preambolo udp.

CHECKSUM
Campo checksum del preambolo udp.


Torna all'indice


ESEMPIO DI PROGRAMMA IN C CHE SFRUTTANDO LE SOCKET RAW FABBRICA UN PACCETTO TCP CON IL FLAG FIN SETTATO A 1 (LINUX KNIGHTS).




 /*******************************************************************************************
 * Questo programma tcp.c genera un pacchetto tcp con il flag fin = 1 ( chiusura connessione)
 * il programma usa socket raw per la creazione del pacchetto tcp che viene infine inviato,
 * con la chiamata sendto().
 * n.b. Dalla linea di comando viene modificato il propio IP-number!!!
 *
 * Il programma deve essere lanciato in questo:
 * tcp porta_sorgente indirizzo_sorgente porta_remota indirizzo_remoto
 *
 * Il programma viene compilato nel seguente modo:
 * gcc -o tcp tcp.c
 *
 ******************************************************************************************/

 /* Dichiarazione degli header principali usati nel programma */

 #include <stdlib.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netinet/in_systm.h>
 #include <netinet/ip.h>
 #include <netinet/tcp.h>
 #include <string.h>
 #include <unistd.h>
 #include <time.h>

 /* Dichiarazione dei prototipi delle funzioni */

 void tcp_gen(char *packet,unsigned short sport,
 unsigned short dport,unsigned long seq,
 unsigned long ack);

 void udp_gen(char *packet,unsigned short sport,
 unsigned short dport,unsigned short length);

 void ip_gen(char *packet,unsigned char protocol,struct in_addr saddr,
 struct in_addr daddr,unsigned short length);

 unsigned short trans_check( unsigned char proto, char *packet,
 int length, struct in_addr source_address,
 struct in_addr dest_address);

 unsigned short in_cksum(unsigned short *addr,int len);

 #define IPVERSION 4 /* versione del pacchetto IP */
 #define DEFAULT_TTL 60 /* Definizione del tempo di vita dei pacchetti */
 #define TH_OFFSET 5
 #define TCP_WINDOW_SIZE 512 /* larghezza della finestra del tcp */

 /* struttura per il calcolo del checksum sul pacchetto tcp*/

 struct psuedohdr {
 struct in_addr source_address;
 struct in_addr dest_address;
 unsigned char place_holder;
 unsigned char protocol;
 unsigned short length;
 } psuedohdr;

 /*********************************************************************************/
 /*********************** calcolo del checksum sul tcp header *********************/
 /*********************************************************************************/

 unsigned short trans_check(unsigned char proto,
 char *packet,
 int length,
 struct in_addr source_address,
 struct in_addr dest_address)
 {
 char *psuedo_packet;
 unsigned short answer;

 /* compilazione della struttura pseudohdr tcp */
 psuedohdr.protocol = proto;
 psuedohdr.length = htons(length);
 psuedohdr.place_holder = 0;
 psuedohdr.source_address = source_address;
 psuedohdr.dest_address = dest_address;

 /* allocazione della struttura pseudohdr */
 if((psuedo_packet = malloc(sizeof(psuedohdr) + length)) == NULL) {
 perror("malloc");
 exit(1);
 }

 /* copia la struttura pseudohdr nella blocco puntato da pseudo_packet */
 memcpy(psuedo_packet,&psuedohdr,sizeof(psuedohdr));
 
 /* applica l'algoritmo del checksum allo pseudo pacchetto*/
 answer = (unsigned short)in_cksum((unsigned short *)psuedo_packet,
 (length + sizeof(psuedohdr)));
 free( psuedo_packet ) ;
 return answer;
 }

 /*********************************************************************************/
 /************************* algoritmo del checksum ********************************/
 /*********************************************************************************/

 unsigned short in_cksum(unsigned short *addr,int len)
 {
 register int sum = 0;
 u_short answer = 0;
 register u_short *w = addr;
 register int nleft = len;

 /*
 * Questo ciclo somma nel registro accumulatore (sum) 32 bit le parole di
 * 16 bit che vengono estratte dalla struttura addr.
 */
 while (nleft > 1) {
 sum += *w++;
 nleft -= 2;
 }

 /* se la lunghezza del campo dati è numero dispari viene completato */
 /* con un 0 addizionale .*/
 if (nleft == 1) {
 *( u_char * )( &answer ) = *( u_char * )w ;
 sum += answer;
 }

 /* viene calcolato il complemento a 1 della somma delle word */
 sum = (sum >> 16) + (sum & 0xffff);
 sum += (sum >> 16);
 answer = ~sum;
 return(answer);
 }

 /*********************************************************************************/
 /*********************** generatore pacchetto IP *********************************/
 /*********************************************************************************/

 void ip_gen(char *packet,unsigned char protocol,struct in_addr saddr,
 struct in_addr daddr,unsigned short length)
 {

 /* definisco un puntatore alla struttura */
 struct iphdr *iphdr;

 /* La variabile pacchetto diventa puntatore alla struttura iphdr */
 iphdr = (struct iphdr *)packet;
 memset((char *)iphdr,'\0',sizeof(struct iphdr));

 /* compilo la struttura ip */

 iphdr->ihl = 5;
 iphdr->version = IPVERSION;
 /* BIG ENDIAN e LIITLE ENDIAN */
 #ifdef IP_LEN_HORDER
 iphdr->tot_len = length;
 #else
 iphdr->tot_len = htons(length);
 #endif /* IP_LEN_HORDER */

 iphdr->id = htons(getpid());
 iphdr->ttl = DEFAULT_TTL;
 iphdr->protocol = protocol;
 iphdr->saddr = saddr.s_addr;
 iphdr->daddr = daddr.s_addr;

 /* calcola il checksum del pacchetto*/
 iphdr->check = (unsigned short)in_cksum((unsigned short *)iphdr,
 sizeof(struct iphdr));
 return;
 }

 /*********************************************************************************/
 /*************** dichiarazione del prototipo della funzione tcp_gen **************/
 /*********************************************************************************/

 void tcp_gen(char *packet,unsigned short sport,
 unsigned short dport,unsigned long seq,
 unsigned long ack)
 {
 /* puntatore alla struttura tcphdr */
 struct tcphdr *tcp;

 /* Cast di packet da (char *) a (struct tcphdr*) */
 tcp = (struct tcphdr *)packet;
 memset((char *)tcp,'\0',sizeof(struct tcphdr));

 /* compilazione della struttura tcp */

 tcp->source = htons(sport);
 tcp->dest = htons(dport);
 tcp->seq = htonl(seq);
 tcp->ack_seq = htonl(ack);
 tcp->res1 = 0;
 tcp->doff = TH_OFFSET;
 tcp->window = htons(TCP_WINDOW_SIZE);
 tcp->fin = 1;

 return;
 }
 /*********************************************************************************/
 /********************************** M A I N **************************************/
 /*********************************************************************************/

 int main(int argc,char *argv[])
 {
 /* vettore pacchetto di lunghezza ip header + tcp header */
 unsigned char packet[ sizeof(struct iphdr) + sizeof(struct tcphdr) ];

 /* sockaddr_in struttura della socket */
 struct sockaddr_in mysocket;

 /* porte sorgenti e destinazioni */
 unsigned short sport, dport;

 /* struttura per l'indirizzamento ID/RETE ID/HOST */
 struct in_addr saddr, daddr;

 /* puntatore alla struttura tcphdr */
 struct tcphdr *tcp;
 unsigned long seq, ack;
 int sockd, on = 1;

 /* Prelevo gli argomenti dalla riga di comando */
 if(argc < 5) {
 fprintf(stderr,"usare: %s porta_sorg. indirizzo_sorg. porta_dest.
 indirizzo_dest\n", argv[0]);
 exit(1);
 }

 /* Compila la struttura per le socket con porta sorgente
 e indirizzo sorgente */
 sport = (unsigned short)atoi(argv[1]);
 saddr.s_addr = inet_addr(argv[2]);

 /* Compila la struttura per le socket con porta destinazione e
 indirizzo destinazione */
 dport = (unsigned short)atoi(argv[3]);
 daddr.s_addr = inet_addr(argv[4]);

 /* Creo il socket descriptor con l'opzione socket raw */
 if((sockd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW)) < 0) {
 perror("socket");
 exit(1);
 }

 /* Setto alcune opzioni inerenti al livello del protocollo IP */
 if(setsockopt(sockd,IPPROTO_IP,IP_HDRINCL,(char *)&on,sizeof(on)) < 0) {
 perror("setsockopt");
 exit(1);
 }

 /* Genero numero di sequenza ed ack casuali */
 srand(getpid());
 seq = rand()%time( NULL );
 ack = rand()%time( NULL );

 /* Genero pacchetto IP */
 ip_gen(packet,IPPROTO_TCP,saddr,daddr,sizeof(packet));

 /* Sposto il puntatore della struttura tcp header della dimensione del */
 /* pacchetto + la struttura ip header */
 tcp = (struct tcphdr *)(packet + sizeof(struct iphdr));

 /* Genero il pacchetto TCP */
 tcp_gen((char *)tcp,sport,dport,seq,ack);

 /* checksum calcolato sul pacchetto IP */
 tcp->check = trans_check(IPPROTO_TCP,(char *)tcp,
 sizeof(struct tcphdr),
 saddr,
 daddr);

 /*inizializza la struttura mysocket */
 memset(&mysocket,'\0',sizeof(mysocket));

 /*Compilo la struttura mysocket */
 mysocket.sin_family = AF_INET;
 mysocket.sin_port = htons(dport);
 mysocket.sin_addr = daddr;

 /* Spedisco il pacchetto da me creato con il flag fin a a 1
 secondo i parametri immessi da linea di comando.*/
 if(sendto(sockd,&packet,sizeof(packet),0x0,(struct sockaddr *)&mysocket,
 sizeof(mysocket)) != sizeof(packet)) {
 perror("sendto");
 exit(1);
 }
 exit(0);
 }


Torna all'indice


PROGRAMMA CHE SFRUTTANDO LE SOCKET RAW CREA UN SEMPLICE SNIFFER



/*Sniffo: sniffer per sistemi UNIX by Pietro Bertera il prog crea un log chiamato sniffolog.log MOLTO grande!!!*/

#include <signal.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>

#define MAX_PACK 65535

char *getip (u_long in);

int
main ()
{

  int sd, pid, number;
  FILE *fp;
  struct
    {
      struct iphdr ip;
      struct tcphdr tcp;
      char data[MAX_PACK - sizeof (struct iphdr) - sizeof (struct tcphdr)];
    } Packet;

  if (geteuid ())
    {
      fprintf (stderr, "Mi dispiace ma non sei root ...\n");
      exit (-1);
    }

  switch (pid = fork ())
    {
    case 0:
      break;
    default:
      printf ("Sniffer by PedroBert - gira in background sul processo %d\n", pid);
      exit (0);
    }

  sd = socket (AF_INET, SOCK_RAW, IPPROTO_TCP);
  number=0;
  while (sd)
    {
      pid = read (sd, &Packet, sizeof (Packet));
      if (pid > 1 && Packet.ip.protocol == IPPROTO_TCP)
        {
          number++;
          fp = fopen ("sniffolog.log", "a");
          fprintf(fp, "PACKET NUMBER: %d\n",number);
          fprintf(fp, "SOURCE IP: %s\n",getip(Packet.ip.saddr));
          fprintf(fp, "SOURCE PORT: %d\n",ntohs(Packet.tcp.source));
          fprintf(fp, "DESTINATION IP: %s\n",getip(Packet.ip.daddr));
          fprintf(fp, "DESTINATION PORT: %d\n",ntohs (Packet.tcp.dest));
          fprintf(fp, "FIN: %d\n", Packet.tcp.fin);
          fprintf(fp, "SYN: %d\n", Packet.tcp.syn);
          fprintf(fp, "RST: %d\n", Packet.tcp.rst);
          fprintf(fp, "ACK: %d\n", Packet.tcp.ack);
          fprintf(fp, "URG: %d\n", Packet.tcp.urg);
          fprintf(fp, "WIN: %d\n", Packet.tcp.window);
          fprintf(fp, "CHECK: %d\n", Packet.tcp.check);
          fprintf(fp, "DATA: %s\n", Packet.data);
          fprintf(fp, "_______________________________________________________________\n");
          fclose (fp);
          usleep(100000);
        }

    }

  fprintf (stderr, "Errore delle Raw Socket\n");
  return 0;
}

char *getip ( u_long in)
{
  struct in_addr s;
  s.s_addr = in;
  return inet_ntoa(s);
}


Torna all'indice


I programmi sono da utilizzare solo per scopi didattici, l'autore non si assume nessuna responsabilità di danni causati da un utilizzo errato di essi e/o delle nozioni sopra descritte.
LA RIPRODUZIONE DI QUESTO DOCUMENTO O DI SUE PARTI E' LIBERAMENTE CONSENTITA ALLE PERSONE FISICHE, PURCHE' NON EFFETTUATA A FINI DI LUCRO