GETADDRINFO(3) | Руководство программиста Linux | GETADDRINFO(3) |
getaddrinfo, freeaddrinfo, gai_strerror - трансляция сетевого адреса и службы
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h>
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);
const char *gai_strerror(int errcode);
Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):
getaddrinfo(), freeaddrinfo(), gai_strerror(): начиная c glibc 2.22: _POSIX_C_SOURCE >= 200112L в glibc 2.21 и старее: _POSIX_C_SOURCE
По заданным node и service, определяющим узел и службу Интернета, getaddrinfo() возвращает одну или несколько структур addrinfo, каждая из которых содержит Интернет-адрес, который можно передавать в вызов bind(2) или connect(2). Функция getaddrinfo() объединяет возможности, предоставляемые функциями gethostbyname(3) и getservbyname(3) в одном интерфейсе, но в отличие от этих функций, getaddrinfo() реентерабельна и позволяет программам не зависеть от IPv4 или IPv6.
Структура addrinfo, используемая в getaddrinfo(), содержит следующие поля:
struct addrinfo { 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; };
Аргумент hints указывает на структуру addrinfo, которая определяет критерии выбора структур адреса сокета, возвращаемых в списке, указанном в res. Если hints не является NULL, то он указывает на структуру addrinfo. В ней с помощью ai_family, ai_socktype и ai_protocol определяются критерии, ограничивающие набор адресов сокета, возвращаемых getaddrinfo():
Все остальные поля структуры, указываемой hints, должны содержать 0 или указатель null, соответственно.
Указание в hints значения NULL эквивалентно установке ai_socktype и ai_protocol равными 0; ai_family присваивается AF_UNSPEC; ai_flags присваивается (AI_V4MAPPED | AI_ADDRCONFIG) (в POSIX указаны другие значения по умолчанию для ai_flags; смотрите ЗАМЕЧАНИЯ). В node задаётся либо числовой сетевой адрес (для IPv4 это цифро-точечная нотация, которую поддерживает inet_aton(3); для IPv6 это строка в шестнадцатеричном формате, который поддерживает inet_pton(3)), либо сетевое имя узла, для которого в дальнейшем определяется адрес. Если в hints.ai_flags содержится флаг AI_NUMERICHOST, тогда node должен иметь цифровой формат сетевого адреса. При AI_NUMERICHOST любые возможные преобразования сетевого адреса узла подавляются.
Если в hints.ai_flags указан флаг AI_PASSIVE и node равно NULL, то возвращаемые адреса сокета будут пригодны для bind(2) сокета, который принимает соединение с помощью accept(2). Возвращаемый адрес сокета будет содержать «шаблонный адрес» (INADDR_ANY для адресов IPv4, IN6ADDR_ANY_INIT для адреса IPv6). Шаблонный адрес используется в приложениях (обычно, серверах), которым нужно принимать подключения с любых сетевых адресов узлов. Если node не равно NULL, то флаг AI_PASSIVE игнорируется.
Если флаг AI_PASSIVE отсутствует в hints.ai_flags, то возвращаемые адреса сокета будут пригодны для использоваться в connect(2), sendto(2), или sendmsg(2). Если node равно NULL, то сетевому адресу будет назначен адрес кольцевого интерфейса (loopback) (INADDR_LOOPBACK для адресов IPv4, IN6ADDR_LOOPBACK_INIT для адреса IPv6); это используется в приложениях, которым нужно связываться с партнёрами, запущенными на том же узле.
В service задаётся порт для каждой возвращаемой адресной структуры. Если этот аргумент — имя службы (смотрите services(5)), то он транслируется в соответствующий номер порта. Также, данный параметр может быть указан в виде десятичного числа, которое просто преобразуется в двоичную форму. Если service равно NULL, то номер порта возвращаемых сокетных адресов остаётся неинициализированным. Если в hints.ai_flags указан флаг AI_NUMERICSERV и service не равно NULL, то значение service должно указывать на строку с числовым номером порта. Данный флаг используется для запрета вызова службы определения имён, если известно, что она не требуется.
Либо node, либо service (но не оба одновременно) могут быть равны NULL.
Функция getaddrinfo() выделяет место и инициализирует связный список структур addrinfo, по одной на каждый сетевой адрес, который совпадает с node и service, в соответствии с любыми ограничениями, наложенными hints, и возвращает указатель на начало списка в res. Элементы в связном списке связаны через поле ai_next.
Существует несколько причин того, почему связный список может содержать более одной структуры addrinfo: сетевой узел имеет несколько адресов, доступен по нескольким протоколам (например, AF_INET и AF_INET6); служба доступна через несколько типов сокетов (например, один её адрес — SOCK_STREAM, а второй — SOCK_DGRAM). Обычно, приложение должно стараться использовать адреса в том порядке, в котором они получены. Функция сортировки, используемая в getaddrinfo(), определена в RFC 3484; в системе порядок может быть изменён через файл /etc/gai.conf (доступен, начиная с glibc 2.5).
Если в hints.ai_flags выставлен флаг AI_CANONNAME, то в поле ai_canonname первой из структур addrinfo возвращаемого списка задаётся указатель официального имени узла.
Остальные поля каждой возвращаемой структуры addrinfo инициализируются следующим образом:
Если в hints.ai_flags содержится флаг AI_ADDRCONFIG, то адреса IPv4, возвращаются в списке, на который указывает res, только, если в локальной системе настроен, как минимум, один адрес IPv4, и адреса IPv6 возвращаются только, если в локальной системе настроен, как минимум, один адрес IPv6. Кольцевой (loopback) адрес в этом случае не учитывается как настроенный. Этот флаг полезен, например в только IPv4-системах, чтобы getaddrinfo() не возвращал сокетные адреса IPv6, с которыми невозможно выполнить connect(2) или bind(2).
Если в hints.ai_flags содержится флаг AI_V4MAPPED и в hints.ai_family задан AF_INET6, и не найдено подходящих адресов IPv6, то в списке, на который указывает res, возвращаются IPv6 адреса отображённых адресов IPv4. Если в hints.ai_flags указаны и AI_V4MAPPED, и AI_ALL, то в списке, на который указывает res, возвращаются и адреса IPv6 и IPv6 адреса отображённых адресов IPv4. Флаг AI_ALL игнорируется, если с ним не задан AI_V4MAPPED.
Функция freeaddrinfo() освобождает память, которая была выделена для динамического связного списка res.
Начиная с glibc 2.3.4, getaddrinfo() был расширен для выборочного прозрачного разрешения исходящих и входящих адресов в формате интернациональных доменных имен (IDN, см. RFC 3490, Internationalizing Domain Names in Applications (IDNA)). Было определено четыре новых флага:
В случае успеха getaddrinfo() возвращает 0, либо один из следующие ненулевых кодов ошибки:
Функция gai_strerror() транслирует эти коды ошибок в читаемый формат, подходящий для сообщений об ошибке.
/etc/gai.conf
Описание терминов данного раздела смотрите в attributes(7).
Интерфейс | Атрибут | Значение |
getaddrinfo() | Безвредность в нитях | MT-Safe env locale |
freeaddrinfo(), gai_strerror() | Безвредность в нитях | MT-Safe |
POSIX.1-2001, POSIX.1-2008. Функция getaddrinfo() описана в RFC 2553.
getaddrinfo() поддерживает нотацию address%scope-id для указания IPv6 scope-ID.
AI_ADDRCONFIG, AI_ALL и AI_V4MAPPED доступны, начиная с glibc 2.3.3. AI_NUMERICSERV доступен, начиная с glibc 2.3.4.
Согласно POSIX.1, при указании hints в NULL ai_flags должен предполагаться как равный 0. Вместо этого библиотека GNU C в данном случае предполагает значение (AI_V4MAPPED | AI_ADDRCONFIG), так как данное значение было принято в качестве улучшения спецификации.
Следующие программы демонстрируют использование getaddrinfo(), gai_strerror(), freeaddrinfo() и getnameinfo(3). Это программы эхо-сервера и клиента UDP-дейтаграмм.
#include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <netdb.h> #define BUF_SIZE 500 int main(int argc, char *argv[]) { struct addrinfo hints; struct addrinfo *result, *rp; int sfd, s; struct sockaddr_storage peer_addr; socklen_t peer_addr_len; ssize_t nread; char buf[BUF_SIZE]; if (argc != 2) { fprintf(stderr, "Usage: %s port\n", argv[0]); exit(EXIT_FAILURE); } memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Разрешены IPv4 и IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Сокет для дейтаграмм */ hints.ai_flags = AI_PASSIVE; /* Для wildcard IP-адреса */ hints.ai_protocol = 0; /* Любой протокол */ hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; s = getaddrinfo(NULL, argv[1], &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); } /* getaddrinfo() возвращает список структур адресов. Идет проверка каждого адреса до успешного bind(2). Если socket(2) (или bind(2)) терпит неудачу, мы (закрываем сокет и) пробуем следующий. */ for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) break; /* Успех */ close(sfd); } if (rp == NULL) { /* Нет успешных адресов */ fprintf(stderr, "Could not bind\n"); exit(EXIT_FAILURE); } freeaddrinfo(result); /* Больше не нужен */ /* Читаем дейтаграмму и пересылаем ее назад отправителю */ for (;;) { peer_addr_len = sizeof(struct sockaddr_storage); nread = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &peer_addr, &peer_addr_len); if (nread == -1) continue; /* Игнорируем запрос с ошибкой */ char host[NI_MAXHOST], service[NI_MAXSERV]; s = getnameinfo((struct sockaddr *) &peer_addr, peer_addr_len, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICSERV); if (s == 0) printf("Получено %zd байт из %s:%s\n", nread, host, service); else fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s)); if (sendto(sfd, buf, nread, 0, (struct sockaddr *) &peer_addr, peer_addr_len) != nread) fprintf(stderr, "Ошибка отправки ответа\n"); } }
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define BUF_SIZE 500 int main(int argc, char *argv[]) { struct addrinfo hints; struct addrinfo *result, *rp; int sfd, s, j; size_t len; ssize_t nread; char buf[BUF_SIZE]; if (argc < 3) { fprintf(stderr, "Usage: %s host port msg...\n", argv[0]); exit(EXIT_FAILURE); } /* Получаем адрес(а), соответствующие узлу/порту */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Разрешены IPv4 и IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Сокет дейтаграмм */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Любой протокол */ s = getaddrinfo(argv[1], argv[2], &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); } /* getaddrinfo() возвращает список структур адресов. Идет проверка каждого адреса до успешного connect(2). Если socket(2) (или connect(2)) терпит неудачу, мы (закрываем сокет и) пробуем следующий. */ for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) break; /* Успех */ close(sfd); } if (rp == NULL) { /* Нет успешных адресов */ fprintf(stderr, "Could not connect\n"); exit(EXIT_FAILURE); } freeaddrinfo(result); /* Больше не нужен */ /* Отправляем оставшиеся аргументы командной строки в виде отдельных дейтаграмм и ждем ответа от сервера */ for (j = 3; j < argc; j++) { len = strlen(argv[j]) + 1; /* +1 для завершающего null-байта */ if (len > BUF_SIZE) { fprintf(stderr, "Игнорируем длинное сообщение в аргументе %d\n", j); continue; } if (write(sfd, argv[j], len) != len) { fprintf(stderr, "partial/failed write\n"); exit(EXIT_FAILURE); } nread = read(sfd, buf, BUF_SIZE); if (nread == -1) { perror("read"); exit(EXIT_FAILURE); } printf("Получено %zd байт: %s\n", nread, buf); } exit(EXIT_SUCCESS); }
getaddrinfo_a(3), gethostbyname(3), getnameinfo(3), inet(3), gai.conf(5), hostname(7), ip(7)
2019-03-06 | GNU |