a.a. 2003/04 Interazione con il DNS Conversioni di Nomi ed Indirizzi Prof. Vincenzo Auletta auletta@dia.unisa.it http://www.dia.unisa.it/professori/auletta/ Università degli studi di Salerno Laurea in Informatica 1 Il DNS consente di convertire dinamicamente nomi di dominio in indirizzi IP e viceversa L interazione con il name server avviene tramite l API resolver l applicazione interagisce con il name server utilizzando le funzioni dell API il funzionamento delle funzioni è modificabile tramite file di configurazione /etc/resolv.conf /etc/hosts, /etc/services, ecc. Il resolver è utilizzato in generale per calcolare corrispondenze tra nomi ed indirizzi nomi di e numeri di porta 2 Funzioni del resolver struct hostent* gethostbyname(const char* hostname); struct hostent* gethostbyaddr(const char* addr, size_t len, int family); Restituisce NULL se errore puntatore diverso da NULL se OK Permettono di interrogare il resolver per effettuare conversioni da nomi ad indirizzi gethostbyname: nome indirizzo gethostbyaddr: indirizzo nome 3 Struttura hostent Le funzioni gethostbyname e gethostbyaddr restituiscono puntatori ad oggetti hostent allocati staticamente struct hostent { char* h_name; /* nome canonico */ char** h_aliases; /* elenco di alias */ int h_addrtype; /* tipo di indirizzo */ int h_length; /* lunghezza dell indirizzo */ char** h_addr_list; /* elenco indirizzi */ ; #define h_addr h_addr_list[0] h_aliases e h_addr_list sono array di stringhe terminati da una stringa nulla
Esempio Funzionamento del Resolver 4 oggetto restituito da gethostbyname per il nome koala un nome canonico (bsdi), due alias (koala e panda) e tre indirizzi IP 4 (140.252.1.11, 140.252.3.54, 140.252.4.54) h_name h_aliases h_addrtype h_length h_addr_list AF_INET 4 bsdi koala panda NULL 140.252.1.11 140.252.3.54 140.252.4.54 NULL 5 Il resolver può operare sia localmente che interagendo con il DNS il file /etc/resolv.conf contiene gli indirizzi dei name server da contattare e le regole di espansione il file /etc/hosts contiene un elenco delle corrispondenze nome/indirizzo il file di configurazione (/etc/resolv.conf) stabilisce le regole di funzionamento del resolver per default contatta prima i name server e poi legge il file locale Errori In caso di errore le funzioni del resolver scrivono un codice nella variabile h_errno la funzione hstrerror() legge il valore di h_errno e restituisce un messaggio di errore appropriato i valori che può assumere h_errno sono definiti in <netdb.h> HOST_NOT_FOUND TRY_AGAIN NO_RECOVERY NO_ADDRESS (NO_DATA) 6 Il nome specificato è sconosciuto Un errore temporaneo si è verificato su un name server Un errore irrimediabile si è verificato su un name server Il nome specificato è valido ma non ha un indirizzo IP 7 Conversioni Servizi/Porte struct servent* getservbyname(const char* servname, const char* protoname); struct servent* getservbyaddr(int port, const char* protoname); Permettono di interrogare il resolver per effettuare conversioni nomi di servizi/numeri di porta getservbyname: nome porta getservbyaddr: porta nome Il resolver non interagisce con il DNS ma legge soltanto il contenuto di un file locale (/etc/services)
Struttura servent Altri Tipi di Interrogazioni 8 Le funzioni getservbyname e getservbyaddr restituiscono puntatori ad oggetti servent allocati staticamente struct servent { char* s_name; /* nome ufficiale del */ char** s_aliases; /* elenco di alias */ int s_port; /* numero di porta */ char* s_proto; /* protocollo da utilizzare */ ; 9 struct netent* getnetbyname(const char* netname); struct netent* getnetbyaddr(long net, int type); struct protoent* getprotobyname(const char* protoname); struct protoent* getprotobynumber(int proto); consentono di assegnare dei nomi a reti e protocolli funzioni poco utilizzate Il resolver legge i file locali (/etc/networks e /etc/protocols) 10 Altre Funzioni void sethostent(int flag); void endhostent(void); #include <unistd.h> #include <sys/utsname.h> int gethostname(char* name, size_t len); /* name contiene il risultato */ sethostent(true) consente di utilizzare TCP per interrrogare il DNS endhostent() ripristina l uso di UDP gethostname() restituisce il nome dell host corrente stesso risultato ottenuto con uname() 11 Client daytime con resolver 1 int main(int argc, char **argv) { int sockd, n; char recvline[maxline + 1], str[maxline]; struct sockaddr_in servaddr; struct in_addr **elenco_addr; struct hostent *hp; struct servent *sp; if (argc!= 3) (1) err_quit("utilizzo: dtime_client <nome dell'host> <nome del >"); if ( (hp = gethostbyname(argv[1])) == NULL) (2) err_quit("errore nella gethostbyname per l'host %s: %s", argv[1], hstrerror(h_errno)); if ( (sp = getservbyname(argv[2], "tcp")) == NULL)(3) err_quit("errore nella getservbyname per il %s", argv[2]); elenco_addr = (struct in_addr **) hp->h_addr_list; 1.legge da linea di comando il nome del server e del 2.recupera l elenco di indirizzi IP del server 3.recupera il numero di porta ed il tipo di protocollo
12 Client daytime con resolver 2 for ( ; *elenco_addr!= NULL; elenco_addr++) { (4) if( (sockd = socket(af_inet, SOCK_STREAM, 0)) < 0 ) err_sys("errore nella socket"); (5) bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; (6) servaddr.sin_port = sp->s_port; memcpy(&servaddr.sin_addr, *elenco_addr, sizeof(struct in_addr)); if (connect(sockd, (SA *) &servaddr, sizeof(servaddr)) == 0) (7) break; err_ret("connessione non riuscita con %s:%d", inet_ntop(hp->h_addrtype, *elenco_addr, str, sizeof(str)), ntohs(sp->s_port)); close(sockd); (8) 4. prova a connettersi a tutti gli indirizzi forniti dal resolver 5. crea il socket 6. riempie servaddr 7. tenta di connettersi e se ci riesce esce dal ciclo 8. altrimenti chiude il socket 13 Client daytime con resolver 3 if (*elenco_addr == NULL) (9) err_quit("impossibile stabiliree una connessione"); while ( (n = read(sockd, recvline, MAXLINE)) > 0) { (10) recvline[n] = 0; if( fputs(recvline, stdout) == EOF ) err_sys("errore in fputs"); exit(0); 9. se è uscito dal ciclo senza essere riuscito a stabilire la connessione esce 10.legge la data e la stampa 14 Server daytime con resolver 1 int main(int argc, char **argv) { pid_t pid; int listend, connd; struct sockaddr_in servaddr; char buff[maxline]; time_t ticks; struct servent *sp; if( (listend = socket(af_inet, SOCK_STREAM, 0)) < 0) err_sys("errore in socket"); if( (sp = getservbyname("daytime", "tcp")) == NULL ) { fprintf(stderr, "errore nella getservbyame perr il %s", argv[2]); exit(-1); /* riempie la struttura servaddr */ servaddr.sin_port = sp->s_port; if( (bind(listend, (SA *) &servaddr, sizeof(servaddr))) < 0) err_sys("errore in bind"); 15 Server daytime con resolver 2 if( listen(listend, 5) < 0 ) err_sys("errore in listen"); for ( ; ; ) { if( (connd = accept(listend, (struct sockaddr *) NULL, NULL)) < 0) err_sys("errore in accept"); if( (pid = fork()) == 0 ) { /* il figlio chiude il socket di ascolto */ ticks = time(null); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); if( write(connd, buff, strlen(buff))!= strlen(buff) ) err_sys("errore in write"); /* il figlio chiude il socket di connessione */ exit(0); /* il padre chiude il socket di connessione */
16 Funzioni Indipendenti dal Protocollo Le funzioni gethostbyname e gethostbyaddr dipendono dal tipo di indirizzi utilizzati e sono obsolete Per utilizzare l indirizzo restituito da gethostbyname dobbiamo sapere a che famiglia appartiene per copiarlo nel campo opportuno di struct sockaddr Restituisce Lo standard POSIX 1.g ha introdotto due nuove funzioni per interfacciarsi con il resolver Sono indipendenti dal tipo di indirizzi Restituiscono informazioni immediatamente utilizzabili dall applicazione L applicazione non deve sapere nulla di indirizzi, protocolli, ecc. 17 Funzione getaddrinfo int getaddrinfo(const char* host, const char* serv, const struct addrinfo *hints, struct addrinfo **result); Diverso da 0 se errore 0 se OK Consente di fare interrogazioni sia per nomi di host che di servizi contemporaneamente Legge dalla struttura puntata da hints il tipo di informazioni richieste restituisce il risultato nella lista puntata da result Struttura addrinfo Esempio 18 struct addrinfo { int ai_flags; /* nome canonico */ int ai_family; /* famiglia di indirizzi */ int ai_socktype; /* tipo di socket */ int ai_protocol; /* tipo di protocollo */ size_t ai_addrlength; /* lunghezza di ai_addr */ char* ai_canonname; /* puntatore al nome ufficiale */ struct sockaddr *ai_addr; /* puntatore all indirizzo struct addrinfo *ai_next; /* puntatore al nodo succ */ ; Tutti i puntatori indirizzano memoria allocata dinamicamente L applicazione deve preoccuparsi di rilasciare la memoria La funzione freeaddrinfo() rilascia tutta la memoria associata ad un oggetto addrinfo 19 Richiediamo informazioni sul daytime e l host koala Specifichiamo che vogliamo indirizzi IP 4 e trasporto TCP HINTS ai_flag AI_CANONNAME ai_family AF_INET ai_socktype SOCK_STREAM ai_protocol 0 ai_addrlen 0 ai_canonname NULL ai_addr NULL ai_next NULL RESULTS ai_flag 0 ai_family AF_INET ai_socktype SOCK_STREAM ai_protocol 0 ai_addrlen 16 ai_canonname bsdi.xyz.com ai_addr 140.252.1.11 ai_next AF_INET 7 prossimo nodo
20 Errori In caso di errore la funzione getaddrinfo() restituisce un intero nonnegativo la funzione gai_strerror() legge il valore restituito dalla funzione e restituisce un messaggio di errore appropriato EAI_ADDRFAMILY EAI_AGAIN EAI_FAIL EAI_BADFLAGS EAI_FAMILY EAI_MEMORY EAI_NODATA EAI_NONAME EAI_SERVICE EAI_SOCKTYPE EAI_SYSTEM tipo di indirizzo non supportato per name un errore temporaneo si è verificato su un name server un errore irrecuperabile si è verificato su un name server valore non corretto di ai_flags valore di ai_family non supportato errore di allocazione della memoria nessun indirizzo associato a name name o service non conosciuto service non supportato per ai_socktype ai_socktype non supportato errore di sistema restituito in errno 21 Funzione getnameinfo int getaddrinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, size_t hostlen, char* serv, size_t servlen, int flags); Restituisce -1 se errore 0 se OK Consente di trasformare l indirizzo del socket contenuto in sockaddr in due stringhe host contiene il nome dell host e serv contiene il nome del hostlen e servlen sono le lunghezze allocate per le stringhe flags consente di modificare il comportamento di default della funzione Utilizzo di getaddrinfo() Schema Base 22 E possibile costruire delle funzioni che nascondono all applicazione tutti i dettagli relativi alla gestione del socket L applicazione usa solo i nomi dell host e del Le funzioni utilizzano le informazioni restituite da getaddrinfo() per invocare le funzioni sul socket Useremo le seguenti funzioni tcp_connect() (client TCP) tcp_listen() (server TCP) udp_client() (client UDP non connesso) udp_connect() (client UDP connesso) udp_server() (server UDP) 23 Tutte le funzioni adotteranno il seguente schema Leggono da linea di comando nome dell host e del È possibile anche specificare l indirizzo IP o il numero di porta Invocano getaddrinfo() Scorrono la lista di risultati e provano ad utilizzare ognuno degli indirizzi Appena ne trovano uno funzionante escono Se nessuno funziona escono con un errore
24 Tcp_connect() 1 int tcp_connect(const char *host, const char *serv) { int sockd, n; struct addrinfo hints, *risp, *backup; char buff[maxline]; bzero(&hints, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; (1) hints.ai_socktype = SOCK_STREAM; if ( (n = getaddrinfo(host, serv, &hints, &risp))!= 0) (2) err_quit("errore in tcp_connect per %s:%s: %s", host, serv, gai_strerror(n)); backup = risp; 1.Riempie i campi di hints 2.Invoca getaddrinfo Risultato restituito nella lista puntata da risp 25 Tcp_connect() 2 do { (3) sockd = socket(risp->ai_family, risp->ai_socktype, risp->ai_protocol); if (sockd < 0) continue; if (connect(sockd, risp->ai_addr, risp->ai_addrlen) < 0) /* ignora questo indirizzo e chiude il socket */ break; while ( (risp = risp->ai_next)!= NULL); if (risp == NULL) err_sys("errore in tcp_connect per %s:%s", host, serv); freeaddrinfo(backup); (4) return(sockd); 3. Cicla su tutti gli indirizzi di risp Prova ad eseguire prima socket() e poi connect() ed esce se ha stabilito la connessione 4. Dealloca la memoria dinamica Tcp_listen() 1 Tcp_listen() 2 26 Int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp) { int listend, n; const int on = 1; struct addrinfo hints, *risp, *backup; bzero(&hints, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ( (n = getaddrinfo(host, serv, &hints, &risp))!= 0) err_quit("errore in tcp_listen per %s:%s: %s", host, serv, gai_strerror(n)); backup = risp; 1.Richiede socket passivo 2.Invoca getaddrinfo 27 do { listend = socket(risp->ai_family, risp->ai_socktype, risp->ai_protocol); if (listend < 0) continue; if( setsockopt(listend, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) (3) err_sys("errore in setsockopt"); if (bind(listend, risp->ai_addr, risp->ai_addrlen) < 0) /* ignora questo indirizzo e chiude il descrittore. */ break; while ( (risp = risp->ai_next)!= NULL); if (risp == NULL) err_quit("errore in tcp_listen per %s:%s", host, serv); if( listen(listend, LISTENQ) < 0 ) err_sys("errore in listen"); (4) if (addrlenp) *addrlenp = risp->ai_addrlen; (5) freeaddrinfo(backup); (6) return(listend); 3. Setta il socket per poter utilizzare una porta occupata 4. Se socket() e bind() riescono chiama la listen() 5. Restituisce in addrlenp un puntatore alla lunghezza dell indirizzo assegnato al socket 6. Dealloca la memoria dinamica
28 Client daytime con tcp_connect() 1 int main(int argc, char **argv) { int sockd, n; char recvline[maxline + 1]; socklen_t len; struct sockaddr *sa; 1.legge da linea di comando il nome del server e del 2.recupera l elenco di indirizzi IP del server 3.recupera il numero di porta ed il tipo di protocollo if (argc!= 3) (1) err_quit("utilizzo: dtime_name_client <nome dell'host> <nome del >"); sockd = tcp_connect(argv[1], argv[2]); if( (sa = malloc(maxsockaddr) == NULL ) err_sys( errore in malloc ); len = MAXSOCKADDR; if( getpeername(sockd, sa, &len) < 0 ) err_sys( errore in getpeername ); printf 29 Client daytime con tcp_connect 2 while ( (n = read(sockd, recvline, MAXLINE)) > 0) { (10) recvline[n] = 0; if( fputs(recvline, stdout) == EOF ) err_sys("errore in fputs"); exit(0); 9. se è uscito dal ciclo senza essere riuscito a stabilire la connessione esce 10.legge la data e la stampa 30 Server daytime con tcp_listen 1 int main(int argc, char **argv) { pid_t pid; int listend, connd; struct sockaddr *cliaddr; socklen_taddrlen, len; struct linger ling; char buff[maxline]; time_t ticks; switch(argc) { (1) case 2: listend = tcp_listen(null, argv[1], &addrlen); break; case 3: listend = tcp_listen(argv[1], argv[2], &addrlen); break; default: err_quit("utilizzo: dtime_name_server [<host>] < o porta>"); if( (cliaddr = malloc(addrlen)) == NULL ) err_sys("errore in malloc"); (2) 1. legge da linea di comando il nome del server e del 2. Alloca la memoria per contenere l indirizzo del client 31 Server daytime con tcp_listen 2 for ( ; ; ) { len = addrlen; if( (connd = accept(listend, cliaddr, &len)) < 0) err_sys("errore in accept"); ling.l_onoff = 1; ling.l_linger = 0; if( setsockopt(connd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) < 0 ) err_sys("errore in setsockopt"); if( (pid = fork()) == 0 ) { /* il processo figlio chiude il socket di ascolto */ ticks = time(null); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); if( write(connd, buff, strlen(buff))!= strlen(buff) ) err_sys("errore in write"); /* il processo figlio chiude il socket di connessione */ exit(0); /* il processo padre chiude il socket di connessione */
Client e Server UDP Dal sito è possibile scaricare il codice delle funzioni udp_client(), udp_server() e udp_connect() logica simile a tcp_connect() e tcp_listen() Il codice di un client ed un server UDP indipendenti dal protocollo 32