Programmare i Socket
Transcript
Programmare i Socket
Programmare i Socket Aspinall & MasterShadow 21 agosto 2003 1 Programmare i Socket S.P.I.N.E Group Indice 1 Il modello Client/Server 3 2 Il modello OSI/ISO 3 3 Il protocollo TCP/IP 4 4 Il protocollo IP 4.1 Struttura dell’header IPv4 . . . . . . . . . . . . . . . . . . . 5 5 Il protocollo TCP 5.1 Struttura dell’header TCP . . . . . . . . . . . . . . . . . . . 8 6 Il protocollo UDP 6.1 Struttura dell’header UDP . . . . . . . . . . . . . . . . . . . 11 7 Le funzioni principali 7.1 Socket(2) . . . . . 7.2 bind(2) . . . . . . . 7.3 listen(2) . . . . . . 7.4 accept(2) . . . . . 7.5 connect(2) . . . . . 7.6 send(2) . . . . . . 7.7 recv(2) . . . . . . . 7.8 gethostbyname(3) 7.9 gethostbyaddr(3) . 12 8 Le strutture di rete 8.1 SockAddr . . . 8.2 SockAddr_In . 8.3 HostEnt . . . . 8.4 In_addr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 8 11 12 13 14 14 15 16 17 18 18 20 . . . . 20 9 Programmi dimostrativi 9.1 UDP Packet sender . . . . . . . . . . . . . . . . . . . . . . 9.2 MiniTalk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.3 DNSLookup . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 21 21 22 23 25 Programmare i Socket S.P.I.N.E Group 1 Il modello Client/Server Il tcp/ip e il modello client/server sono la base di internet. La comunicazione tra questi e‘ effettuata grazie ad applicazioni definite come: applicazioni socket. Questi possono essere programmi come telnet,ssh,ftp e quant’altro, e la loro comunicazione e‘ permessa dalle API socket. Le API socket, chiamate anche interfacce socket, vennero sviluppate nei laboratori dell’universita‘ di Berkeley, ecco perche‘ spesso li abbiamo letti come socket Berkeley. Il paradigma client-server si basa sull’assunto che esiste un certo insieme di entita‘ che ha bisogno di ottenere un certo servizio da un’altra entita‘ e che esiste un meccanismo di comunicazione a scambio di messaggi. Ad ogni entita‘ che richiede il servizio viene dato il nome di client, mentre a quella che lo offre il nome di server. Con il termine entita‘ si intende un’ampia casistica che puo‘ andare da un host in rete fino a un thread. Nel seguito intenderemo in modo implicito che client e server siano dei task o dei thread. 2 Il modello OSI/ISO Questo modello e‘ stato creato con lo scopo di soddisfare tutte le specifiche, non soltanto apparecchiature di rete ma anche pc, software, hardware e sistemi operativi. Questi standard sono concepiti seguendo un modello concettuale, strutturato in 7 strati: Livello 7 Livello 6 Livello 5 Livello 4 Livello 3 Livello 2 Livello 1 Applicazione Presentazione Sessione Trasporto Rete Collegamento dati - Datalink Fisico Livelli superiori (5-7) Livelli inferiori (1-4) Una connessione per far si che venga instaurata e quindi venga effettuato lo scambio dei dati deve soddisfare questi strati. Ogni strato superiore utilizza i dati provenienti dagli strati inferiori. Iniziamo dagli strati inferiori: Strato Fisico. Questo strato , il piu‘ basso, ha il compito di definire un bit ( tramite la cifratura binaria 1 e 0. Ad esempio la frequenza e la lunghezza, l’assenza di segnale, la tensione, le interfaccie di connessioni 3 Programmare i Socket S.P.I.N.E Group ( spine, plug ), lunghezze massime e minime di cavi... sono definite in questo strato. Strato Datalink. Questo strato impacchetta i dati e li invia al mezzo di trasmissione suddivisi in frame.In questo strato viene assegnato un indirizzo fisico ad ogni link. Un esempio di questo e‘ il MAC ( Media Access Controll ) Strato Rete. Questo strato assegna un indirizzo unico ad ogni unita‘ e grazie ei routed protocol ( IP, IPX ) e routing protocol ( RIP, OSPF), viene indicato al dato la strada da seguire per arrivare a destinazione.I dati vengono spediti in pacchetti. Strato Trasporto. In questo strato ci si prepara alla trasmissione dei dati.Lo strato di trasporto gestisce il controllo del flusso dei dati,il checksum ( controllo dell’integrita‘ del dato ) e divide i dati in segmenti appropiati agli strati inferiori. TCP e UDP lavorano ad esempio in questo livello. I dati viaggiano in datagrammi. Questo datagramma e‘ formato da un header ( intestazione ) ed e‘ seguito dai dati. Gli strati superiori in ordine sessione, presentazione e applicazione si occupano di sincronizzare la connessione, convertire i dati in formato standard in modo che le interfaccie ( API ) possano comunicare indipendentemente dal software. 3 Il protocollo TCP/IP Il protocollo TCP/IP consente a computer di tutti i tipi di comunicare fra loro. La sua diffusione e‘ stata molto piu‘ ampia di quanto di prevedeva. E‘ stato sviluppato negli anni settanta come un progetto di ricerca per lo scambio di messaggi fra le reti ed oggi e‘ la piu‘ diffusa forma di comunicazione delle reti di computer. Si tratta di un protocollo aperto e molte sue implementazioni sono liberamente accessibili ed ha fondato le basi per quello che attualmente chiamiamo Internet. Il protocollo TCP/IP deriva dai due protocolli principali di Internet, progettati nel 1974 da Kahn e Cerf presso l’universita‘ di Berkeley ed e‘ suddiviso in 4 livelli che descrivono il processo di trasmissione ereditando parte del modello OSI/ISO, composto da 7 livelli. Livello 4 Applicazione Livello 3 Trasporto Livello 2 Rete (network) Livello 1 Collegamento (link) 4 Programmare i Socket S.P.I.N.E Group Il livello Collegamento (link), a volte chiamato data-link, solitamente include il driver del dispositivo di collegamento e la corrispodente interfaccia di rete del computer. Questo livello gestisce gli standard per gli hardware di rete. Il livello Rete (network) gestisce lo scambio dei pacchetti nella rete. L’instradamento dei pacchetti viene effettuato in questo livello e trovano posto in questo strato dello stack TCP/IP il protocollo IP, ICMP e IGMP, propriamente incapsulati dalla suite di protocolli TCP/IP. Il livello Trasporto fornisce un flusso di dati tra due host per il livello superiore. Nel protocollo TCP/IP possono essere utilizzati il protocollo TCP ed il protocollo UDP. Il livello Applicazione gestisce qualsiasi applicazione che possa implementare la suite TCP/IP. La struttura del modello TCP/IP prevede una rete formata da sottoreti che comunicano tramite gateway. Ogni macchina ha un indirizzo univoco a 32 bit rappresentato da una serie 4 di numeri da 0 a 255 separati da un punto. Ogni sottorete e‘ definita da una quaterna di numeri da 0 a 255, in cui lo 0 specifica quale parte dell’indirizzo non e‘ stabilita a priori (puo‘ cambiare a seconda delle condizioni). 4 Il protocollo IP Il protocollo IP e‘ la base del TCP/IP. Tutti i protocolli incapsulabili dal protocollo TCP/IP vengono trasmessi come datagrammi IP, i quali hanno la particolarita‘ di non effettuare una connessione fra gli host. I datagrammi IP non danno infatti garanzie sul fatto che il pacchetto arrivi a destinazione ma offrono una semplice gestione degli errori basata sull’invio di messaggi ICMP di diagnostica. Per avere maggiore sicurezza sulla trasmissione dei dati e‘ bene passare ad un livello superiore (TCP). Il protocollo IP non fornisce nessuna informazione su eventuali datagrammi successivi ed ogni datagrammi e‘ gestito indipendentente dagli altri datagrammi. Questo comporta che l’ordine di arrivo non puo‘ essere previsto. 5 Programmare i Socket 4.1 S.P.I.N.E Group Struttura dell’header IPv4 L’intestazione (header) di un datagramma IPv4 e‘ solitamente di 20 bytes tranne nei casi in cui siamo presenti le opzioni del datagramma. I byte vengono inviati secondo il modo di ordinamento Big Endian ovvero in ordine di successione nello schema sovrastante. I primi 4 bit, ossia il campo Version, sono dedicati alla versione del protocollo IP; in questo caso stiamo analizzando un pacchetto IPv4. Header Length corrisponde alla lunghezza dell’intestazione del pacchetto ed essendo limitata a 4 bit possiamo avere al massimo 60 byte di header (15 x 4). Gli 8 bit individuati dal campo Type Of Service (TOS) settano le impostazioni del datagramma IP che possono essere: minimize delay (minimizza il ritardo), maximize throughput (massimizza le prestazioni), maximize reliability (massimizza l’affidabilita‘), minimize monetary cost (minimizza il costo). Solo uno di questi quattro bit puo‘ essere portato ad 1 mentre gli altri bit devono essere obbligatoriamente a 0. Se tutti e quattro i bit sono a 0 si sta lavorando nelle condizioni normali. Ricordiamo che il TOS non e‘ piu‘ considerato valido dalla nuove implementazioni del protocollo TCP. Il campo Total Lenght Field rappresenta la lunghezza totale del datagramma IP espressa in byte. Combinando questo campo con quello della lunghezza dell’intestazione possiamo trovare esattamente dove cominciano i dati. Possiamo avere al massimo datagrammi di 65535 byte che vengono rappresentati dai 16 bit del campo ma e‘ bene forgiare pacchetti di dimensione minore per evitare la frammentazione durante il tragitto da un host all’altro. Il campo Identification identifica univocamente ogni datagramma IP inviato 6 Programmare i Socket S.P.I.N.E Group da un host. Generalmente viene incrementato di un’unita‘ ogni volta che viene inviato un pacchetto. A questo punto troviamo 3 flags, dei quali uno e‘ settato a 0 di default. Il flag DF (Don’t Fragment) e‘ un bit che impone ai sistemi di non frammentare il pacchetto (ritorna un errore ICMP in caso di frammentazione obbligatoria) mentre il flag MF (More Fragments) dice al sistema che sono in arrivo nuovi frammenti del datagramma. Il campo Fragment Offset contiene l’offset del frammento rispetto all’inizio del datagramma originale. Il campo Time-To-Live (TTL) imposta un limita massimo agli instradamenti effettuabili nel tragitto da un host all’altro. Questo campo limita la vita di un datagramma IP. Viene inizializzato durante la creazione del pacchetto ad un valore predefinito e viene decrementato ad ogni passaggio attraverso un router. Quando il TTL si azzera il pacchetto viene scartato e viene inviato un segnale di diagnostica ICMP. Il protocollo IP puo‘ ospitare altri tipi di protocollo nei suoi campi. Questa procedura viene chiamata incapsulamento ed e‘ molto diffusa nel web perche‘ consente una grande flessibilita d’opera. Il campo Protocol e‘ stato implementato per consentire questa procedura e viene usato per il demultiplexing dei datagrammi incapsulati. II campo Header Checksum e‘ una somma di controllo per evitare errori e viene calcolata solo sulla base dell’header. I campi Source IP Address e Destination IP Address contengono l’indirizzo IP del mittente e del destinatario espressi in una variabile long a 32 bit. 7 Programmare i Socket S.P.I.N.E Group 5 Il protocollo TCP Il protocollo TCP e‘ indubbiamente il protocollo di alto livello piu‘ usato. Questo protocollo e‘ ingrado di trasformare l’inaffidabile servizio a pacchetti offerto dal layer Network dello stack TCP/IP in un affidabile flusso di byte. Le innovazioni introdotte da questo protocollo sono state notevoli. Prima di tutto la connessione fra gli host deve essere effettuata e i sistemi devono assicurarsi che la connessione possa essere stabilita accidentalmente. Il flusso di dati in uscita deve essere spezzato presso il lato del mittente e deve essere riassemblato presso il destinatario. Il sistema che riceve il flusso di dati deve prendersi cura della rimozione dei pacchetti duplicati e di correggere l’ordine di arrivo, se necessario. Per stabilire una connessione il protocollo TCP utilizza un meccanismo di handshaking, chiamato three-way handshake (stretta di mano a 3 vie :-). I pacchetti duplicati o scaduti vengono gestiti usando un numero di sequenza generato dal sistema mittente. I pacchetti dispersi vengono controllati dal sistema destinatario tramita un meccanismo di riconoscimento e in caso di perdite viene richiesto l’invio al mittente. Il protocollo TCP fornisce inoltre un sistema di controllo di flusso per prevenire la congestione in caso di sistemi con diverse velocita‘. 5.1 Struttura dell’header TCP Il protocollo TCP viene solitamente utilizzato in segmenti di dati incapsulati nei datagrammi IP e per essere utilizzato il datagramam deve essere demultiplexato dal sistema che riceve il pacchetto. L’intestazione di un segmento TCP va quindi ad accodarsi all’intestazione del datagramma IP, nella sezione Data. Ecco come si presenta l’header di un segmento TCP secondo lo standard: 8 Programmare i Socket S.P.I.N.E Group Ogni segmento TCP possiede due campi dove sono specificati la porta sorgente e la porta di destinazione, che combinati con le informazioni del datagramma IP rendono ogni connessione univoca. Sono due campi da 16 bit quindi le porte indirizzabili sono 65535. Il campo Sequence Number identifica i byte nel flusso di dati fra il mittente ed il destinatario. Il protocollo TCP segna ogni byte con un numero di sequenza calcolato dal sistema. Ad ogni nuova connessione, il segmento TCP inviato contiene il numero di sequenza iniziale scelto dall’host per questa connessione. Ogni segmento e‘ quindi identificato da un sequence number. Per ordinare i dati abbiamo bisogno di un riferimento che ci dica qual e‘ il segmento seguente a quello appena arrivato. Questo compito e‘ svolto dal campo Acknowledgment Number , che memorizza il numero di sequenza del pacchetto seguente. Il campo Header Lenght indica la dimensione dell’intestazione in parole binarie di 32 bit. L’header puo‘ quindi essere al massimo di 60 byte, anche se nella norma e‘ di 20 byte. Dopo un campo di 6 bit riservato troviamo una serie di bit che definiscono i flag del protocollo TCP; analizziamoli insieme: URG: il protocollo TCP fornisce una modalita‘ chiamata Urgent Mode, che consente ad uno dei due estremi di dire all’altro che sono stati messi dati urgenti nel flusso di dati. Questo flag ci dice che bisogna controllare l’Urgent Pointer. ACK : Il numero di riconoscimento (Acknowledge Number) e‘ valido. PSH: Il ricevente dovrebbe passare il dato segnato da questo flag prima possibile. RST : Reimposta la connessione 9 Programmare i Socket S.P.I.N.E Group SYN: Sincronizza i numeri di sequenza per dare vita ad una nuova connessione. FIN: Il mittente ha concluso l’invio di dati. Il protocollo TCP fornisce un meccanismo di controllo del flusso (Flow Control) composto da un campo chiamato Window size. In questo campo viene messo il numero di byte che il destinatario e‘ disposto ad accettare. Possono essere accettati al massimo 65535 byte (dovuti alla limitazione di 16 bit del campo). Il campo Checksum prende in considerazione si l’intestazione che i dati ed e‘ un dato di controllo che deve essere calcolato e scritto dal mittente e verificato dal destinatario. L’Urgent Pointer e‘ valido solo se il flag URG e‘ impostato. Questi rappresenta un offset che deve essere aggiunto al numero di sequenza per capire quando terminano i dati importanti. 10 Programmare i Socket S.P.I.N.E Group 6 Il protocollo UDP Le applicazioni che non hanno bisogno delle funzionalita‘ offerte dal protocollo TCP, possono usare un protocollo alternativo chiamato UDP. L’invio dei dati, coeerenti con il protocollo UDP, avviene passando un’intestazione seguita dai dati in un datagramma IP. UDP e‘ un servizio che non fornisce garanzie sull’arrivo a destinazione dei dati e i datagrammi possono essere riorganizzati e duplicati. 6.1 Struttura dell’header UDP Il protocollo UDP, come il TCP, identifica i processi di invio e di ricezione tramite l’utilizzo di numeri di porta. Nonostante sia una caratteristica comune dei due sistemi di trasmissione le porte TCP sono totalmente indipendenti dalle porte UDP, poiche‘ durante il demultiplex del datagramma IP il campo Protocol permette di separare i due protocolli. Il campo Lenght rappresenta l’intera lunghezza del datagramma UDP e la sua dimensione minima e‘ di 8 byte. E‘ infatti ammissibile avere un datagramma UDP senza dati. Questo campo e‘ rindondante in quanto le informazioni necessarie sono presenti nel datagramma IP nel quale e‘ incapsulato. Il campo Checksum viene calcolato sia sull’header che sui dati e puo’ contenere dati non reali ed e‘ quindi bene effettuare il padding a passi di 16 bit aggiungendo 0 (NULL) nel campo data. 11 Programmare i Socket S.P.I.N.E Group 7 Le funzioni principali 7.1 Socket(2) Sintassi: int socket( int domain, int type, int protocol ); Valori di ritorno: Minore di 0 se si verifica un errore Maggiore o uguale a 0 se ritorna un file descriptor Alla funzione socket(2), che si occupa di definire il protocollo, devono essere passati tre parametri: domain Definisce un dominio di comunicazione, ovvero la famiglia del protocollo che sara‘ usato per la comunicazione. PF_INET : ipv4 protocollo internet PF_INET6 : ipv6 protocollo internet PF_UNIX : Comunicazione locale PF_NS : Protocolli di Xerox NS PF_IMPLINK : Livello di collegamento IMP *PF (protocol family) e AF (address family) sono equivalenti. type Indica il tipo di socket. SOCK_STREAM : streaming connection (TCP) SOCK_DGRAM : datagram communication (UDP) SOCK_RAW : Accesso "grezzo" al protocollo di rete (IP) protocol Indica il particolare protocollo utilizzato dal socket: il sistema usa il protocollo piu‘ adatto al tipo di socket. IPPROTO_UDP: udp IPPROTO_TCP: tcp IPPROTO_ICMP: icmp IPPROTO_RAW: ip 12 Programmare i Socket S.P.I.N.E Group Esempio socket(2): int sock;if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) perror(Error socket()); 7.2 bind(2) Sintassi: int bind (int sock, struct sockaddr * my_addr, int addrlen); Valore di ritorno: 0 in caso di successo -1 se si verifica un errore La funzione bind(2) associa alla socket un indirizzo locale, usata dal lato server specifica la porta su cui si pone in ascolto. A questa funzione vengono passati tre parametri : sock Socket descriptor restituito dalla funzione socket(2). *my_addr Puntatore all’indirizzo della struttura sockaddr_in. addrlen Dimensione dell’ indirizzo della struttura sockaddr_in. Esempio bind() : struct sockaddr_in myaddr; my_addr.sin_family = AF_INET; my_addr.sin_port = htons(PORTA); my_addr.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY sta per qualsiasi indirizzo bzero(&(my_addr.sin_zero), 8); if (bind(sock, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) perror(Errore bind()); 13 Programmare i Socket S.P.I.N.E Group /* sizeof : Funzione che indica la grandezza in bytes di un qualunque tipo di dato. */ 7.3 listen(2) Sintassi: int listen(int sock, int backlog); Valore di ritorno: 0 in caso di successo -1 se si verifica un errore La funzione listen(2) usata dal lato server setta quante connessioni potra‘ contenere la coda di backlog. Questa viene usata con 2 parametri: sock Il solito file descriptor. backlog Indica il numero di connessioni che potranno sostare nella coda. Esempio listen(2): if (listen 7.4 (sock, 5) == -1) perror(Error listen()); accept(2) Sintassi: int accept(int sock, struct sockaddr *addr, int *addrlen); Valori di ritorno: Minore di 0 se si verifica un errore Maggiore o uguale a 0 se ritorna un file descriptor 14 Programmare i Socket S.P.I.N.E Group La funzione accept(2) si occupa di accettare la prima connessione in listen, questa ritorna un socket descriptor del tutto nuovo ma uguale al primo, che si occupera‘ di riceve e mandare dati. Questa ha 3 parametri : sock Il solito file descriptor. *addr Puntatore all’indirizzo della struttura sockaddr_in. In questa struttura viene restituito l’indirizzo dell’host mittente. *addrlen Dimensione dell’indirizzo della struttura sockaddr_in Esempio accept(2): int sin_size; sin_size = sizeof(struct sockaddr_in); new_fd = accept(sockfd, (struct sockaddr *)&their_ addr, &sin_size); printf(ricevuto da: %s,inet_ntoa(their_addr)); /* inet_ntoa e` una funzione che prende come parametro un unsigned long in network byte order e ritorna un array di char che rappresenta l'ip un formato dotted decimal*/ 7.5 connect(2) Sintassi: int connect(int sock, struct sockaddr *serv_addr, int addrlen); Valore di ritorno: 0 in caso di successo -1 se si verifica un errore La funzione connect(2) permette di inizializzare la connessione. Deve essere chiamata con tre parametri: 15 Programmare i Socket S.P.I.N.E Group sock solito file descriptor. *serv_addr Puntatore alla struttura sockaddr_in,dove viene letto l’host a cui collegarsi. addrlen Dimensione dell’ indirizzo della struttura sockaddr_in. Esempio connect(2) : int sock; struct sockaddr_in server; if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) perror(Error socket()); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(PORTA); my_addr.sin_addr.s_addr = inet_addr(212.122.223.56); connect(sock, (struct sockaddr *)&server, sizeof(server)); // inet_addr: funzione che converte un indirizzo ip // in un unsigned long. 7.6 send(2) Sintassi: int send(int sock, const void *msg, int len, int flags); Valore di ritorno: 0 in caso di successo -1 se si verifica un errore La funzione send(2) permette l’invio di dati ed ha bisogno dei seguenti parametri: sock file descriptor. 16 Programmare i Socket S.P.I.N.E Group *msg puntatore al msg che si vuole spedire. len grandezza in bytes del msg. flags eventuali opzioni da settare,diversamente settarlo a 0. Esempio send() : char *msg=ciao; send(sockfd, msg, sizeof(msg), 0); 7.7 recv(2) Sintassi: int recv(int sock, void *buf, int len, unsigned int flags); Valore di ritorno: 0 in caso di successo -1 se si verifica un errore La funzione recv permette la ricezione dai dati inviati ad un client. Gli argomenti sono i seguenti: sock file descripton. buffer puntatore. len grandezza in bytes del msg arrivato. flags eventuali opzini di ricezione (settarlo a 0). 17 Programmare i Socket 7.8 S.P.I.N.E Group gethostbyname(3) Sintassi: struct hostent *gethostbyname (char *name) La funzione gethostbyname(3) permetta la risoluzione di un host in un indirizzo ip. Questa restitusce un puntatore alla struttura hostent,in caso di errore restitusce NULL. Esempio: struct hostent *host; if ((host=gethostbyname(host)) == NULL) herror(gethostbyname); // E` necessario usare la chiamata herror(), perche` la // chiamata errno non viene usata. 7.9 gethostbyaddr(3) Sintassi: struct hostent *gethostbyaddr (const char *addr, int len, int type); La funzione di rete gethostbyaddr(3) fornisce un metodo per la risoluzione inversa del nome a partire dall’indirizzo ip. Questa restitusce un puntatore alla struttura hostent o in caso di errore restitusce NULL. Gli argomenti necessari all’utilizzo della funzione sono: char *addr e‘ un puntatore ad un indirizzo ip (spesso una struttura del tipo in_addr) int len e‘ la lunghezza in byte dell’indirizzo int type e‘ un intero che esprime la tipologia di indirizzo (e‘ attualmente supportato solo il tipo AF_INET). 18 Programmare i Socket S.P.I.N.E Group Esempio di gethostbyaddr(3): struct hostent *host; struct addr_in addr; inet_aton(127.0.0.1, &addr); host = gethostbyaddr((const char *) &addr, sizeof(addr), AF_INET); if (host == NULL) herror(gethostbyaddr); 19 Programmare i Socket S.P.I.N.E Group 8 Le strutture di rete La programmazione con le socket implica l’uso di determinate strutture. Le principali che analizzeremo saranno 3 e siete invitati a studiarle nei relativi header del kernel del sistema operativo. 8.1 SockAddr struct sockaddr { unsigned short sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ }; Questa e‘ la struttura piu‘ generale. sa_family contiene la tipologia di cui fa parte l’indirizzo espressa come unsigned shot mentre sa_data e‘ un array di caratteri di dimensione 14, atto a contenere l’indirizzo. 8.2 SockAddr_In La struttura SockAddr_in prevede principalmente l’utilizzo di tre variabili: short int sin_family contiene la tipologia dell’indirizzo espressa in short integer. unsigned short int sin_port contiene la porta di collegamento all’indirizzo. struct in_addr sin_addr contiene l’indirizzo vero e proprio, incapsulato in una struttura del tipo in_addr. struct sockaddr_in { }; sa_family_t sin_family; u_int16_t sin_port; struct in_addr sin_addr; Vediamo ora come associare ip e porta alla struttura sockaddr_in: struct sockaddr_in their_addr; their_addr.sin_family = AF_INET; their_addr.sin_port = htons(2555); their_addr.sin_addr = htonl(INADDR_ANY); 20 Programmare i Socket S.P.I.N.E Group bzero(&(their_addr.sin_zero), 8); // azzera la restante struttura //INADDR_ANY : costante accetta ogni ip //htons() : converte uno short da Host a Network //htonl() : converte un long da Host a Network 8.3 HostEnt Analizzando la chiamata gethostbyname(3) abbiamo utilizzato la struttura hostent, questo appunto perche‘ la funzione in questione restituisce un puntatore alla struttura hostent. Vediamo di analizzarla meglio : struct hostent char char int int char { *h_name; /* official name of host */ **h_aliases; /* alias list */ h_addrtype; /* host address type */ h_length; /* length of address */ **h_addr_list; /* list of addresses from nameserver */ #define h_addr h_addr_list[0] /* address, for backward compatiblity */ }; La struttura hostent nel nostro caso e‘ riempita dall’argomento passato alla chiamata gethostbyname, ed e‘ indispensabile nel caso volessimo risolvere un host in un indirizzo di rete. Nel char puntato da *h_name e‘ immagazzinato il nome dell’host mentre in **h_addrtype e‘ presente una lista di alias del nome. Il tipo di indirizzo assegnato all’host e‘ espresso dal campo h_addrtype sottoforma di intero, come d’altrocanto la lunghezza dell’indirizzo, immagazzinata in h_lenght. La lista degli indirizzi provenienti dal nameserver viene puntata da **h_addr_list. 8.4 In_addr struct in_addr { unsigned long int s_addr; }; Questa semplice struttura e‘ necessaria per molte chiamate inet_* ovvero tutte le chiamate di manipolazione degli indirizzi internet. La struttura in questione e‘ in grado di memorizzare un indirizzo web nel formato long senza segno. 21 Programmare i Socket S.P.I.N.E Group 9 Programmi dimostrativi 9.1 UDP Packet sender Semplice client che senda un pacchetto udp : /* minitalk codec : [email protected] */ #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <errno.h> <string.h> <sys/types.h> <netinet/in.h> <netdb.h> <sys/socket.h> <sys/wait.h> int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in their_addr; struct hostent *host; int numbytes; if (argc<4) { printf(Usare: exit(-1); } minitalk hostname porta messaggio\n); host=gethostbyname(argv[1]); if (!host) { herror(argv[1]); exit(-1); } if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror(Errore); exit(1); 22 Programmare i Socket S.P.I.N.E Group } their_addr.sin_family = AF_INET; their_addr.sin_port = htons(atoi(argv[2])); their_addr.sin_addr = *((struct in_addr *)host->h_addr); bzero(&(their_addr.sin_zero), 8); if ((numbytes=sendto(sockfd, argv[3], strlen(argv[3]), 0, (struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) { perror(Errore); exit(1); } printf(Sendato il pacchetto a : %s\nporta :%d\n, inet_ntoa(their_addr.sin_addr), atoi(argv[2])); close(sockfd); return 0; } 9.2 MiniTalk Semplice server che stampa i dati ricevuti dal client : /* minitalk codec : #include #include #include #include #include #include #include #include [email protected] */ <stdio.h> <stdlib.h> <errno.h> <string.h> <sys/types.h> <netinet/in.h> <sys/socket.h> <sys/wait.h> #define MAXBYTES 100 23 Programmare i Socket S.P.I.N.E Group int main(int argc, char *argv[]) { int s; struct sockaddr_in my_addr; struct sockaddr_in their_addr; int addr_len, numbytes; int pid; char buf[MAXBYTES]; if (argc < 2) { printf(Usare: minitalk porta\n); exit(1); } printf(Sono in ascolto sulla porta %d\n, atoi(argv[1])); if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror(socket); exit(1); } my_addr.sin_family = AF_INET; my_addr.sin_port = htons(atoi(argv[1])); my_addr.sin_addr.s_addr = INADDR_ANY; bzero(&(my_addr.sin_zero), 8); if (bind(s, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) { perror(Errore); exit(1); } addr_len = sizeof(struct sockaddr); if ((numbytes=recvfrom(s, buf, MAXBYTES, 0,(struct sockaddr *)&their_addr, &addr_len)) == -1) { perror(Errore); exit(1); } printf(ricevuto da : %s\n,inet_ntoa(their_addr.sin_addr)); 24 Programmare i Socket S.P.I.N.E Group buf[numbytes] = '\0'; printf(messaggio : \%s\\n,buf); //close(s); } 9.3 DNSLookup Utile tool per la risoluzione dei nomi e del reverse lookup (da ip a dns) : /* Simple DNSLookup and Whois mailto:[email protected] mailto:[email protected] mailto:[email protected] */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <sys/socket.h> #include <sys/wait.h> #include <assert.h> #define ERROR -1 #define SUCCESS 0 int main(int argc, char *argv[]) { int sockfd; char stringa[100]; struct hostent *host; struct in_addr addr; } if (argc<2) { fprintf(stderr, Sintassi: exit(ERROR); %s <ip/dns>\n, argv[0]); if((host = gethostbyname(argv[1])) == NULL) { 25 Programmare i Socket } } S.P.I.N.E Group herror(DNSLookup); exit(ERROR); if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror(Socket); exit(ERROR); inet_aton(argv[1], &addr); memset(&stringa, 0, sizeof(stringa)); inet_ntop(AF_INET, host->h_addr, stringa, sizeof(stringa)); } if (strcmp(argv[1], stringa) == 0) { memset(&host, 0, sizeof(host)); host = gethostbyaddr((const char *) &addr, sizeof(addr), AF_INET); assert(host->h_addrtype==AF_INET && host->h_length==4); fprintf(stdout, %s -> %s\n, argv[1], host->h_name); } else { fprintf(stdout, %s -> %s\n, argv[1], stringa); } return SUCCESS; 26