11.6. Функция getaddrinfo

11.6. Функция getaddrinfo

Функции gethostbyname и gethostbyaddr поддерживают только IPv4. Интерфейс IPv6 разрабатывался в несколько этапов (история разработки описана в разделе 11.20), и в конечном итоге получилась функция getaddrinfo. Последняя осуществляет трансляцию имен в адреса и служб в порты, причем возвращает она список структур sockaddr, а не список адресов. Такие структуры могут непосредственно использоваться функциями сокетов. Благодаря этому функция getaddrinfo скрывает все различия между протоколами в библиотеке функций. Приложение работает только со структурами адресов сокетов, которые заполняются getaddrinfo. Эта функция определяется стандартом POSIX.

ПРИМЕЧАНИЕ

Определение этой функции в POSIX происходит от более раннего предложения Кейта Склоуэра (Keith Sklower) для функции, называемой getconninfo. Эта функция стала результатом обсуждений с Эриком Олменом (Eric Allman), Вилльямом Дастом (William Durst), Майклом Карелсом (Michael Karels) и Стивеном Вайсом (Steven Wise), а также более ранней реализации, написанной Эриком Олменом. Замечание о том, что указания имени узла и имени службы достаточно для соединения с этой службой независимо от деталей протокола, было сделано Маршалом Роузом (Marshall Rose) в проекте X/Open.

#include <netdb.h>

int getaddrinfo(const char *hostname, const char *service,

 const struct addrinfo *hints, struct addrinfo **result);

Возвращает: 0 в случае успешного выполнения, ненулевое значение в случае ошибки

(см. табл. 11.2).

Через указатель result функция возвращает указатель на связный список структур addrinfo, который задается в заголовочном файле <netdb.h>:

struct addrinfo {

 int    ai_flags;          /* AI_PASSIVE, AI_CANONNAME */

 int    ai_family;         /* AF_xxx */

 int    ai_socktype;       /* SOCK_xxx */

 int    ai_protocol;       /* 0 или IPPROTO_xxx для IPv4 и IPv6 */

 size_t ai_addrlen;        /* длина ai_addr */

 char*  ai_canonname;      /* указатель на каноническое имя узла */

 struct sockaddr *ai_addr; /* указатель на структуру адреса сокета */

 struct addrinfo *ai_next; /* указатель на следующую структуру в связном

                              списке */

};

Переменная hostname — это либо имя узла, либо строка адреса (точечно-десятичная запись для IPv4 или шестнадцатеричная строка для IPv6). Переменная service — это либо имя службы, либо строка, содержащая десятичный номер порта. (См. также упражнение 11.4.)

Аргумент hints — это либо пустой указатель, либо указатель на структуру addrinfo, заполненную рекомендациями вызывающего процесса о типах информации, которую он хочет получить. Например, если заданная служба предоставляется и для TCP, и для UDP (служба domain, которая ссылается на сервер DNS), вызывающий процесс может присвоить элементу ai_socktype структуры hints значение SOCK_DGRAM. Тогда возвращение информации будет иметь место только для дейтаграммных сокетов.

Вызывающим процессом могут быть установлены значения следующих элементов структуры hints:

? ai_flags (несколько констант AI_XXX, объединенных операцией ИЛИ);

? ai_family (значение AF_xxx);

? ai_socktype (значение SOCK_xxx);

? ai_protocol.

Поле ai_flags может содержать следующие константы:

? AI_PASSIVE указывает, что сокет будет использоваться для пассивного открытия;

? AI_CANONNAME указывает функции на необходимость возвратить каноническое имя узла;

? AI_NUMERICHOST запрещает преобразование между именами и адресами. Аргумент hostname должен представлять собой строку адреса;

? AI_NUMERICSERV запрещает преобразование между именами служб и номерами портов. Аргумент service должен представлять собой строку с десятичным номером порта;

? AI_V4MAPPED вместе с ai_family = AF_INET6 указывает функции на необходимость вернуть адреса IPv4 из записей А, преобразованные к IPv6, если записи типа AAAA отсутствуют;

? AI_ALL при указании вместе с AI_V4MAPPED говорит о необходимости вернуть адреса IPv4, преобразованные к IPv6, вместе с истинными адресами IPv6;

? AI_ADDRCONFIG возвращает адреса, относящиеся к заданной версии IP, когда имеется несколько интерфейсов, имеющих IP-адреса другой версии.

Если аргументом структуры hints является пустой указатель, функция подразумевает нулевое значение для ai_flags, ai_socktype и ai_protocol и значение AF_UNSPEC для ai_family.

Если функция завершается успешно (0), то в переменную, на которую указывает аргумент result, записывается указатель на список структур addrinfo, связанных через указатель ai_next. Имеется два способа возвращения множественных структур.

1. Если существует множество адресов, связанных с узлом hostname, то одна структура возвращается для каждого адреса, который может использоваться с запрашиваемым семейством адресов (значение ai_family, если задано).

2. Если служба предоставляется для множества типов сокетов, то одна структура может быть возвращена для каждого типа сокета в зависимости от ai_socktype. (Заметьте, что большинство реализаций getaddrinfo считают, что номер порта используется только тем типом сокета, который запрашивается в ai_socktype. Если аргумент ai_socktype не определен, функция возвращает ошибку.)

Например, если структура hints пуста, а вы запрашиваете записи для службы domain на узле с двумя IP-адресами, возвращаются четыре структуры addrinfo:

? одна для первого IP-адреса и типа сокета SOCK_STREAM;

? одна для первого IP-адреса и типа сокета SOCK_DGRAM;

? одна для второго IP-адреса и типа сокета SOCK_STREAM;

? одна для второго IP-адреса и типа сокета SOCK_DGRAM.

Мы показываем схематическое изображение этого примера на рис. 11.3. Не существует никакого гарантированного порядка структур при возвращении множества элементов. Например, мы не можем считать, что службы TCP возвращаются перед службами UDP.

Рис. 11.3. Пример информации, возвращаемой функцией getaddrinfo

ПРИМЕЧАНИЕ

Хотя это и не гарантируется, реализация должна возвращать IP-адреса в том же порядке, в котором они возвращаются DNS. Некоторые распознаватели позволяют администратору указывать порядок сортировки адресов в файле /etc/resolv.conf. Протокол IPv6 определяет правила выбора адресов (RFC 3484 [28]), которые могут влиять на порядок адресов, возвращаемых getaddrinfo.

Информация, возвращаемая в структурах addrinfo, готова для передачи функциям socket и connect или sendto (для клиента) и bind (для сервера). Аргументы функции socket — это элементы ai_family, ai_socktype и ai_protocol. Второй и третий аргументы функций connect и bind — это элементы ai_addr (указатель на структуру адреса сокета соответствующего типа, заполняемую функцией getaddrinfo) и ai_addrlen (длина этой структуры адреса сокета).

Если в структуре hints установлен флаг AI_CANONNAME, элемент ai_canonname первой возвращаемой структуры указывает на каноническое имя узла. В терминах DNS это обычно полное доменное имя (FQDN). Программы типа telnet широко используют этот флаг для того, чтобы выводить канонические имена систем, к которым производится подключение. Пользователь может указать короткое имя узла или его альтернативное имя, но он должен знать, с какой системой он в результате соединился.

На рис. 11.3 представлена возвращаемая информация для следующего вызова:

struct addrinfo hints, *res;

bzero(&hints, sizeof(hints));

hints.ai_flags = AI_CANONNAME;

hints.ai_family = AF_INET;

getaddrinfo("bsdi", "domain", &hints, &res);

На этом рисунке все, кроме переменной res, относится к динамически выделяемой памяти (например, с помощью функции malloc). Предполагается, что каноническое имя узла freebsd4 — freebsd4.unpbook.com, и что этот узел имеет два адреса IPv4 в DNS.

Порт 53 предназначен для службы domain, и нужно учитывать, что этот номер порта будет представлен в структурах адресов сокетов в сетевом порядке байтов. Мы приводим возвращаемые значения ai_protocol IPPROTO_TCP и IPPROTO_UDP. Функция getaddrinfo может возвращать значение ai_protocol равное 0 для структур SOCK_STREAM, если этого достаточно для однозначного определения протокола (типа сокета недостаточно, например, если в системе помимо TCP реализован и SCTP), и 0 для структур SOCK_DGRAM, если в системе не реализованы другие протоколы дейтаграмм для IP (на момент написания этой книги стандартизованных протоколов еще не было, но два уже разрабатывались IETF). Лучше всего, если getaddrinfo всегда будет возвращать конкретный тип протокола.

В табл. 11.1 показано число структур addrinfo для каждого возвращаемого адреса, определяемое на основе заданного имени службы (которое может быть представлено десятичным номером порта) и рекомендации ai_socktype.

Таблица 11.1. Число структур addrinfo, возвращаемых для каждого IP-адреса

Элемент ai_socktype Служба обозначена именем и предоставляется: Служба обозначена именем порта Только TCP Только UDP Только SCTP TCP и UDP TCP и SCTP TCP, UDP и SCTP 0 1 1 1 2 2 3 Ошибка SOCK_STREAM 1 Ошибка 1 1 2 2 2 SOCK_DGRAM Ошибка 1 1 Ошибка 1 1 SOCK_SEQPACKET Ошибка Ошибка 1 Ошибка 1 1 1

Более одной структуры addrinfo возвращается для каждого IP-адреса только в том случае, когда поле ai_socktype структуры hints пусто и либо служба поддерживается TCP и UDP (как указано в файле /etc/services), либо задан номер порта для этой службы.

Если бы мы рассматривали все 64 возможных варианта сочетаний входных данных для функции getaddrinfo (имеется шесть входных переменных), многие сочетания оказались бы недопустимыми, а некоторые не имели бы смысла. Вместо этого рассмотрим наиболее типичные случаи.

? Задание имени узла и службы. Это традиционный случай для клиента TCP и UDP. По завершении клиент TCP перебирает в цикле все возвращаемые IP-адреса, вызывая функции socket и connect для каждого из них, пока не установится соединение или пока не будут перебраны все адреса. Мы показываем такой пример с нашей функцией tcp_connect в листинге 11.2.

Для клиента UDP структура адреса сокета, заполняемая с помощью функции getaddrinfo, будет использоваться в вызове функции sendto или connect. Если клиент сообщит, что первый адрес не работает (ошибка на присоединенном сокете UDP или тайм-аут на неприсоединенном сокете), будет предпринята попытка обратиться к другому адресу.

Если клиент знает, что он обрабатывает только один тип сокета (например, клиентами Telnet и FTP обрабатываются только сокеты TCP, а клиентами TFTP — только сокеты UDP), то элементу ai_socktype структуры hints должно быть задано соответственно либо значение SOCK_STREAM, либо значение SOCK_DGRAM.

? Типичный сервер задает службу (service), но не имя узла (hostname), и задает флаг AI_PASSIVE в структуре hints. Возвращаемая структура адреса сокета должна содержать IP-адрес, равный INADDR_ANY (для IPv4) или IN6ADDR_ANY_INIT (для IPv6). Сервер TCP затем вызывает функции socket, bind и listen. Если сервер хочет разместить в памяти с помощью функции malloc другую структуру адреса сокета, чтобы получить адрес клиента из функции accept, то возвращаемое значение ai_addrlen задает требуемый для этого размер.

Сервер UDP вызовет функции socket, bind и затем recvfrom. Если сервер хочет разместить в памяти с помощью функции malloc другую структуру адреса сокета, чтобы получить адрес клиента из функции recvfrom, возвращаемое значение ai_addrlen также задает нужный размер.

Как и в случае типичного клиентского кода, если сервер знает, что он обрабатывает только один тип сокета, то элемент ai_socktype структуры hints должен быть задан либо как SOCK_STREAM, либо как SOCK_DGRAM. Это позволяет избежать возвращения множества структур, с (возможно) неверным значением элемента ai_socktype.

? До сих пор мы демонстрировали серверы TCP, создающие один прослушиваемый сокет, и серверы UDP, создающие один сокет дейтаграмм. Это тот вариант, который подразумевался в предыдущем абзаце. Альтернативным устройством является сервер, который обрабатывает множество сокетов с помощью функции select. В этом сценарии сервер должен последовательно перебрать все структуры из списка, возвращаемого функцией getaddrinfo, создать по одному сокету для каждой структуры и вызвать функцию select.

ПРИМЕЧАНИЕ

Проблема этой технологии состоит в том, что условие, по которому функция getaddrinfo возвращает множество структур, возникает, когда служба может обрабатываться как протоколом IPv4, так и протоколом IPv6 (см. табл. 11.3). Но эти два протокола не полностью независимы, как мы увидели в разделе 10.2, то есть если мы создаем прослушиваемый сокет IPv6 для данного порта, нет необходимости создавать для него прослушиваемый сокет IPv4, поскольку соединения, приходящие от клиентов IPv4, автоматически обрабатываются стеком протоколов и прослушиваемым сокетом IPv6, при условии, что параметр сокета IPV6_V6ONLY не установлен.

Невзирая на тот факт, что функция getaddrinfo «лучше», чем функции gethostbyname и gethostbyaddr (помимо того что эта функция упрощает написание кода, не зависящего от протокола, она обрабатывает и имя узла, и имя службы, и к тому же вся возвращаемая ею информация размещается в памяти динамически, а не статически), ее все же не так просто использовать, как это могло показаться. Проблема в том, что нам требуется разместить в памяти структуру hints, инициализировать ее нулем, заполнить необходимые поля, вызвать функцию getaddrinfo и затем пройти весь связный список, проверяя каждый его элемент. В последующих разделах мы предоставим более простые интерфейсы для типичных клиентов TCP и UDP и серверов, которые будем создавать в оставшейся части книги.

Функция getaddrinfo решает проблему преобразования имен узлов и имен служб в структуры адресов сокетов. В разделе 11.17 мы опишем обратную функцию getnameinfo, которая преобразует структуры адресов сокетов в имена узлов и имена служб.

Данный текст является ознакомительным фрагментом.