Опрос данных о сети
Данные о хостах как узлах сети хранятся в сетевой базе, последовательный доступ к которой обслуживается функциями sethostent(), gethostent() и endhostent() (см. пример 11.1).
#include <netdb.h> void sethostent (int stayopen); struct hostent *gethostent (void); void endhostent (void);
Листинг 11.1. Описание функций последовательного доступа к сетевой базе данных о хостах - узлах сети. (html, txt)
Функция sethostent() устанавливает соединение с базой, остающееся открытым после вызова gethostent(), если значение аргумента stayopen отлично от нуля. Функция gethostent() последовательно читает элементы базы, возвращая результат в структуре типа hostent, содержащей по крайней мере следующие поля.
char *h_name; /* Официальное имя хоста */ char **h_aliases; /* Массив указателей на альтернативные */ /* имена хоста, завершаемый пустым */ /* указателем */ int h_addrtype; /* Тип адреса хоста */ int h_length; /* Длина в байтах адреса данного типа */ char **h_addr_list; /* Массив указателей на сетевые адреса */ /* хоста, завершаемый пустым указателем */
Функция endhostent() закрывает соединение с базой.
В пример 11.2 показана программа, осуществляющая последовательный просмотр сетевой базы данных о хостах - узлах сети, а в пример 11.3 приведен фрагмент ее возможной выдачи.
Листинг 11.2. Пример программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети. (html, txt)
Листинг 11.3. Фрагмент возможных результатов работы программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети. (html, txt)
К рассматриваемой базе возможен и случайный доступ по ключам - именам и адресам хостов с помощью функций gethostbyname() и gethostbyaddr(), однако они считаются устаревшими и из новой версии стандарта POSIX могут быть исключены. Вместо них предлагается использовать функции getnameinfo() и getaddrinfo() (см. пример 11.4).
#include <sys/socket.h> #include <netdb.h>
void freeaddrinfo (struct addrinfo *ai);
int getaddrinfo (const char *restrict nodename, const char * restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res);
int getnameinfo (const struct sockaddr *restrict sa, socklen_t salen, char *restrict node, socklen_t nodelen, char *restrict service, socklen_t servicelen, int flags);
Листинг 11.4. Описание функций freeaddrinfo(), getaddrinfo(), getnameinfo(). (html, txt)
Функция getaddrinfo() позволяет по имени узла сети (хоста) (аргумент nodename) и/или имени сетевого сервиса (servname) получить набор адресов сокетов и ассоциированную информацию, что дает возможность создать сокет для обращения к заданному сервису.
Если аргумент nodename отличен от пустого указателя, он способен задавать описательное имя или адресную цепочку. Для адресных семейств AF_INET и AF_UNSPEC (см. ниже описание аргумента hints) именем может служить имя хоста, а адресной цепочкой - стандартные для Internet адреса в точечных обозначениях (например, 193.232.173.17).
При пустом значении nodename подразумевается хост, локальный для вызывающего процесса.
Аргумент servname может задавать имя сервиса или (для адресных семейств AF_INET и AF_UNSPEC) десятичный номер порта. Пустое значение servname означает запрос сетевого адреса.
Аргумент hints позволяет передать дополнительную информацию об опрашиваемом сервисе - адресное семейство, тип сокета, протокол, флаги. Согласно стандарту, структура addrinfo, описанная в заголовочном файле <netdb.h>, должна содержать по крайней мере следующие поля.
int ai_flags; /* Входные флаги */
int ai_family; /* Адресное семейство сокета */
int ai_socktype; /* Тип сокета */
int ai_protocol; /* Протокол сокета */
socklen_t ai_addrlen; /* Длина адреса сокета */
struct sockaddr *ai_addr; /* Адрес сокета */
char *ai_canonname; /* Официальное имя узла сети */
struct addrinfo *ai_next; /* Указатель на следующий элемент списка */
При обращении к функции getaddrinfo() все поля структуры addrinfo, на которую указывает аргумент hints, кроме первых четырех (ai_flags, ai_family, ai_socktype, ai_protocol), должны быть нулевыми или равными NULL.
Значение AF_UNSPEC в поле ai_family подразумевает, что вызывающего устроит любое адресное семейство. Аналогичный смысл имеют нулевые значения полей ai_socktype и ai_protocol. При hints, равном NULL, подразумевается AF_UNSPEC для ai_family и нулевые значения для других полей.
Из флагов, которые могут быть установлены в поле ai_flags, упомянем следующие.
AI_PASSIVE
Если значение аргумента nodename равно NULL, этот флаг игнорируется. В противном случае, если он указан, будет возвращен адрес сокета, предназначенного для принятия входящих соединений.
AI_CANONNAME
Данный флаг предписывает выяснить официальное имя узла сети.
AI_NUMERICHOST
Флаг означает, что хост задан адресной цепочкой, и не допускает использования какого-либо сервиса имен.
AI_NUMERICSERV
Флаг помечает, что сервис (аргумент servname) задан номером порта, и налагает запрет на обращение к какому-либо сервису имен.
AI_NUMERICSERV
Флаг помечает, что сервис ( аргумент servname) задан номером порта, и налагает запрет на обращение к какому-либо сервису имен.
Признаком успешного завершения функции getaddrinfo() является нулевой результат. В таком случае выходной аргумент res будет ссылаться на указатель на список структур типа addrinfo (связанных полем ai_next) - принадлежащие им значения полей ai_family, ai_socktype, ai_protocol пригодны для создания подходящих сокетов с помощью функции socket(), а значения ai_addr и ai_addrlen, в зависимости от флага AI_PASSIVE, могут служить аргументами функций connect() или bind(), применяемых к созданному сокету.
Функция freeaddrinfo() позволяет освободить память, занятую списком структур типа addrinfo и ассоциированными данными.
Функцию getnameinfo() можно считать обратной по отношению к getaddrinfo(). Аргумент sa - входной, он задает транслируемый адрес сокета (salen - размер структуры sockaddr). Аргументы node и service - выходные, задающие адреса областей памяти, куда помещаются, соответственно, имя узла и сервиса; размеры этих областей ограничены значениями nodelen и servicelen.
По умолчанию предполагается, что сокет имеет тип SOCK_STREAM, а кроме того, возвращается полное доменное имя хоста. Аргумент flags позволяет изменить подразумеваемое поведение. Если задан флаг NI_DGRAM, сокет считается датаграммным. При установленном флаге NI_NOFQDN возвращается короткое имя узла. Флаги NI_NUMERICHOST и NI_NUMERICSERV предписывают возвращать числовые цепочки для адресов хоста и сервиса, соответственно.
Обратим внимание на несколько технических деталей. При работе с адресами сокетов вместо родовой структуры типа sockaddr обычно используются более специализированные (описанные в заголовочном файле <netinet/in.h>) - sockaddr_in для адресного семейства AF_INET (IPv4) и sockaddr_in6 для AF_INET6 (IPv6). Первая из них, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля (все с сетевым порядком байт).
sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* Номер порта */ struct in_addr sin_addr; /* IP-адрес */
Структура типа sockaddr_in6 устроена несколько сложнее; мы не будем ее рассматривать.
Структуры типов sockaddr и sockaddr_in (или sockaddr_in6) мысленно накладываются друг на друга, а преобразование типов между ними выполняется по мере необходимости. Отметим также, что тип in_port_t эквивалентен uint16_t, структура типа in_addr содержит по крайней мере одно поле:
in_addr_t s_addr; тип in_addr_t эквивалентен uint32_t.
Техническую роль играют и функции преобразования IP-адресов из текстового представления в числовое и наоборот (см. пример 11.5).
#include <arpa/inet.h> in_addr_t inet_addr (const char *cp); char *inet_ntoa (struct in_addr in); int inet_pton (int af, const char *restrict src, void *restrict dst); const char *inet_ntop (int af, const void *restrict src, char *restrict dst, socklen_t size);
Листинг 11.5. Описание функций преобразования IP-адресов из текстового представления в числовое и наоборот.
Первые две функции манипулируют только адресами IPv4: inet_addr() преобразует текстовую цепочку cp (адрес в стандартных точечных обозначениях) в пригодное для использования в качестве IP-адреса целочисленное значение, inet_ntoa() выполняет обратное преобразование.
Вторая пара функций по сути аналогична первой, но имеет чуть более общий характер, так как способна преобразовывать адреса в формате IPv6. Первый аргумент этих функций, af, задает адресное семейство: AF_INET для IPv4 и AF_INET6 для IPv6. Буфер, на который указывает аргумент dst функции inet_pton() (в него помещается результат преобразования - IP-адрес в числовой двоичной форме с сетевым порядком байт), должен иметь длину не менее 32 бит для адресов IPv4 и 128 бит для IPv6.
Аргумент src функции inet_ntop(), возвращающей текстовое представление, указывает на буфер с IP-адресом в числовой форме с сетевым порядком байт. Аргумент size задает длину выходного буфера, на него указывает аргумент dst. Подходящими значениями, в зависимости от адресного семейства, могут служить INET_ADDRSTRLEN или INET6_ADDRSTRLEN.
Выше было отмечено, что преобразование значений типов uint16_t и uint32_t из хостового порядка байт в сетевой выполняется посредством функций htons() и htonl(); функции ntohs() и ntohl() осуществляют обратную операцию (см. пример 11.6).
#include <arpa/inet.h> uint32_t htonl (uint32_t hostlong); uint16_t htons (uint16_t hostshort); uint32_t ntohl (uint32_t netlong); uint16_t ntohs (uint16_t netshort);
Листинг 11.6. Описание функций преобразования целочисленных значений из хостового порядка байт в сетевой и наоборот.
Использование функции getaddrinfo() вместе с сопутствующими техническими деталями проиллюстрируем программой, показанной в пример 11.7. Возможные результаты ее выполнения приведены в пример 11.8.
#include <stdio.h> #include <netdb.h> #include <arpa/inet.h>
int main (void) { struct addrinfo hints = {AI_CANONNAME, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; struct addrinfo *addr_res;
if (getaddrinfo ("www", "http", &hints, &addr_res) != 0) { perror ("GETADDRINFO"); } else { printf ("Результаты для сервиса http\n"); /* Пройдем по списку возвращенных структур */ do { printf ("Адрес сокета: Порт: %d IP-адрес: %s\n", ntohs (((struct sockaddr_in *) addr_res->ai_addr)->sin_port), inet_ntoa (((struct sockaddr_in *) addr_res->ai_addr)->sin_addr)); printf ("Официальное имя хоста: %s\n", addr_res->ai_canonname); } while ((addr_res = addr_res->ai_next) != NULL); }
return 0; }
Листинг 11.7. Пример программы, использующей функцию getaddrinfo().
Результаты для сервиса http Адрес сокета: Порт: 80 IP-адрес: 193.232.173.1 Официальное имя хоста: t01
Листинг 11.8. Возможный результат работы программы, использующей функцию getaddrinfo().
Завершая изложение серии технических моментов, укажем, что полезным дополнением к функциям getaddrinfo() и getnameinfo() является функция gai_strerror() (см. пример 11.9). Она возвращает текстовую цепочку, расшифровывающую коды ошибок, перечисленные в заголовочном файле <netdb.h>.
Стандарт POSIX- 2001 специфицирует следующие коды ошибок, имена которых говорят сами за себя: EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NONAME, EAI_OVERFLOW, EAI_SERVICE, EAI_SOCKTYPE, EAI_SYSTEM.
#include <netdb.h> const char *gai_strerror (int ecode);
Листинг 11.9. Описание функции gai_strerror().
Если немного модифицировать приведенную выше программу (в пример 11.10 показан измененный фрагмент, где имя сервиса - "HTTP" - задано большими буквами), то в стандартный протокол с помощью функции gai_strerror() будет выдано содержательное диагностическое сообщение (см. пример 11.11). Отметим, что выдача функции perror() в данном случае невразумительна.
. . . int res;
if ((res = getaddrinfo ("www", "HTTP", &hints, &addr_res)) != 0) { perror ("GETADDRINFO"); fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); } else { printf ("Результаты для сервиса HTTP\n"); . . .
Листинг 11.10. Модифицированный фрагмент программы, использующей функции getaddrinfo() и gai_strerror().
GETADDRINFO: No such file or directory GETADDRINFO: Servname not supported for ai_socktype
Листинг 11.11. Диагностические сообщения от функций perror() и gai_strerror(), выданные по результатам работы функции getaddrinfo().
Наряду с базой данных хостов (узлов сети) поддерживается база данных сетей с аналогичной логикой работы и набором функций (см. пример 11.12).
#include <netdb.h> void setnetent (int stayopen); struct netent *getnetent (void); struct netent *getnetbyaddr (uint32_t net, int type); struct netent *getnetbyname (const char *name); void endnetent (void);
Листинг 11.12. Описание функций доступа к базе данных сетей.
Функция getnetent() обслуживает последовательный доступ к базе, getnetbyaddr() осуществляет поиск по адресному семейству (аргумент type) и номеру net сети, а getnetbyname() выбирает сеть с заданным (официальным) именем. Структура типа netent, указатель на которую возвращается в качестве результата этих функций, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.
char *n_name; /* Официальное имя сети */
char **n_aliases; /* Массив указателей на альтернативные */ /* имена сети, завершаемый пустым указателем */
int n_addrtype; /* Адресное семейство (тип адресов) сети */
uint32_t n_net; /* Номер сети (в хостовом порядке байт) */
Точно такой же программный интерфейс предоставляет база данных сетевых протоколов (см. пример 11.13).
#include <netdb.h> void setprotoent (int stayopen); struct protoent *getprotoent (void); struct protoent *getprotobyname (const char *name); struct protoent *getprotobynumber (int proto); void endprotoent (void);
Листинг 11.13. Описание функций доступа к базе данных сетевых протоколов.
Структура типа protoent содержит по крайней мере следующие поля.
char *p_name; /* Официальное имя протокола */
char **p_aliases; /* Массив указателей на альтернативные */ /* имена протокола, завершаемый пустым */ /* указателем */
int p_proto; /* Номер протокола */
В пример 11.14 показан пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов. пример 11.15 содержит фрагмент возможных результатов работы этой программы.
#include <stdio.h> #include <netdb.h>
int main (void) { struct protoent *pht; char *pct; int i;
setprotoent (1);
while ((pht = getprotoent ()) != NULL) { printf ("Официальное имя протокола: %s\n", pht->p_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->p_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер протокола: %d\n\n", pht->p_proto); }
if ((pht = getprotobyname ("ipv6")) != NULL) { printf ("Номер протокола ipv6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол ip в базе не найден\n"); }
if ((pht = getprotobyname ("IPV6")) != NULL) { printf ("Номер протокола IPV6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол IPV6 в базе не найден\n"); }
endprotoent ();
return 0; }
Листинг 11.14. Пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.
Официальное имя протокола: ip Альтернативные имена: IP Номер протокола: 0
Официальное имя протокола: icmp Альтернативные имена: ICMP Номер протокола: 1
. . .
Официальное имя протокола: tcp Альтернативные имена: TCP Номер протокола: 6
. . .
Официальное имя протокола: udp Альтернативные имена: UDP Номер протокола: 17
. . .
Официальное имя протокола: ipv6 Альтернативные имена: IPv6 Номер протокола: 41
. . .
Официальное имя протокола: ipv6-crypt Альтернативные имена: IPv6-Crypt Номер протокола: 50
. . .
Официальное имя протокола: visa Альтернативные имена: VISA Номер протокола: 70
. . .
Официальное имя протокола: iso-ip Альтернативные имена: ISO-IP Номер протокола: 80
. . .
Официальное имя протокола: sprite-rpc Альтернативные имена: Sprite-RPC Номер протокола: 90
. . .
Официальное имя протокола: ipx-in-ip Альтернативные имена: IPX-in-IP Номер протокола: 111
. . .
Официальное имя протокола: fc Альтернативные имена: FC Номер протокола: 133
Номер протокола ipv6: 41
Протокол IPV6 в базе не найден
Листинг 11.15. Фрагмент возможных результатов работы программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.
Еще одно проявление той же логики работы - база данных сетевых сервисов (см. пример 11.16).
#include <netdb.h> void setservent (int stayopen); struct servent *getservent (void); struct servent *getservbyname (const char *name, const char *proto); struct servent *getservbyport (int port, const char *proto); void endservent (void);
Листинг 11.16. Описание функций доступа к базе данных сетевых сервисов.
Обратим внимание на то, что в данном случае можно указывать второй аргумент поиска - имя протокола. Впрочем, значение аргумента proto может быть пустым указателем, и тогда поиск производится только по имени сервиса (функция getservbyname()) или номеру порта ( getservbyport()), который должен быть задан с сетевым порядком байт.
Структура типа servent содержит по крайней мере следующие поля.
char *s_name; /* Официальное имя сервиса */
char **s_aliases; /* Массив указателей на альтернативные */ /* имена сервиса, завершаемый пустым */ /* указателем */
int s_port; /* Номер порта, соответствующий сервису */ /* (в сетевом порядке байт) */
char *s_proto; /* Имя протокола для взаимодействия с */ /* сервисом */
В пример 11.17 приведен пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт. В пример 11.18 показан фрагмент возможных результатов работы этой программы.
#include <stdio.h> #include <netdb.h>
int main (void) { struct servent *pht; char *pct; int i;
setservent (1);
while ((pht = getservent ()) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); }
if ((pht = getservbyport (htons ((in_port_t) 21), "udp")) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); } else { perror ("GETSERVBYPORT"); }
if ((pht = getservbyport (htons ((in_port_t) 21), (char *) NULL)) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); } else { perror ("GETSERVBYPORT"); }
endservent ();
return 0; }
Листинг 11.17. Пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.
. . .
Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: tcp
Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: udp
Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp
Официальное имя сервиса: ftp
Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp
. . .
Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: tcp
Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: udp
. . .
Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: tcp
Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: udp
. . .
Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: tcp
Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: udp
. . .
Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: tcp
Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: udp
Официальное имя сервиса: ftp Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp
Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp
Листинг 11.18. Фрагмент возможных результатов работы программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.
Отметим, что при поиске по ключу возвращается первый подходящий элемент базы данных. По этой причине, когда не был задан протокол (второе обращение к функции getservbyport()), в качестве результата был возвращен элемент с протоколом tcp.