444 000 произведений, 109 000 авторов.

Электронная библиотека книг » Уильям Ричард Стивенс » UNIX: разработка сетевых приложений » Текст книги (страница 35)
UNIX: разработка сетевых приложений
  • Текст добавлен: 17 сентября 2016, 20:42

Текст книги "UNIX: разработка сетевых приложений"


Автор книги: Уильям Ричард Стивенс


Соавторы: Эндрю М. Рудофф,Билл Феннер

Жанр:

   

ОС и Сети


сообщить о нарушении

Текущая страница: 35 (всего у книги 88 страниц) [доступный отрывок для чтения: 36 страниц]

11.22. Резюме

Набор функций, вызываемых приложением для преобразования имени узла в IP– адрес и обратно, называется распознавателем. Две функции, gethostbynameи gethostbyaddr, являются типичными точками входа. С переходом на IPv6 и многопоточное программирование полезными становятся getaddrinfoи getnameinfo, способные работать с адресами IPv6 и безопасные в многопоточной среде.

Для работы с именами служб и номерами портов широко используется функция getservbyname, принимающая имя службы и возвращающая структуру, содержащую номер порта. Преобразование чаще всего осуществляется на основании данных, содержащихся в некотором текстовом файле. Существует возможность сопоставления имен и номеров протоколов, а также имен и номеров сетей, но используется она реже.

Альтернативой DNS, которую мы не упомянули, является непосредственный вызов функций распознавателя вместо использования функций gethostbynameи gethostbyaddr. Таким способом пользуется, например, программа sendmail, предназначенная для поиска записи типа MX, чего не может сделать функция gethostby XXX. У функций распознавателя имена начинаются с res_. Примером такой функции является функция res_init, которую мы описали в разделе 11.4. Описание этих функций и пример вызывающей их программы находятся в главе 15 книги [1]. При вводе в командной строке man resolverдолжны отобразиться страницы руководства для этих функций.

Упражнения

1. Измените программу, представленную в листинге 11.1, так, чтобы для каждого возвращаемого адреса вызывалась функция gethostbyaddr, а затем выведите возвращаемое имя h_name. Сначала запустите программу, задав имя узла только с одним IP-адресом, а затем – с несколькими IP-адресами. Что происходит?

2. Устраните проблему, показанную в предыдущем упражнении.

3. Запустите программу, показанную в листинге 11.4, задав имя службы chargen.

4. Запустите программу, показанную в листинге 11.4, задав IP-адрес в точечно– десятичной записи в качестве имени узла. Допускает ли это ваш распознаватель? Измените листинг 11.4, чтобы разрешить IP-адрес в виде строки десятичных чисел с точками в качестве имени узла и строку с десятичным номером порта в качестве имени службы. В каком порядке должно выполняться тестирование IP-адреса для строки в точечно-десятичной записи и для имени?

5. Измените программу в листинге 11.4 так, чтобы можно было работать либо с IPv4, либо с IPv6.

6. Измените программу в листинге 8.5 так, чтобы сделать запрос DNS, и сравните возвращаемый IP-адрес со всеми IP-адресами узла получателя, то есть вызовите функцию gethostbyaddr, используя IP-адрес, возвращаемый функцией recvfrom, а затем вызовите gethostbynameдля поиска всех IP-адресов для узла.

7. Измените листинг 11.6, чтобы вызвать функцию getnameinfoвместо функции sock_ntop. Какие флаги вы должны передать функции getnameinfo?

8. В разделе 7.5 мы обсуждали завладение портом с помощью параметра сокета SO_REUSEADDR. Чтобы увидеть, как это происходит, создайте не зависящий от протокола сервер времени и даты UDP, показанный в листинге 11.13. Запустите один экземпляр сервера в одном окне, свяжите его с универсальным адресом и некоторым портом, который вы выберете. Запустите в другом окне клиент и убедитесь, что этот сервер выполняет обработку клиента (отметьте вызов функции printfна узле сервера). Затем запустите другой экземпляр сервера в другом окне, и на этот раз свяжите его с одним из адресов направленной передачи узла и тем же портом, что и первый сервер. С какой проблемой вы сразу же столкнетесь? Устраните эту проблему и перезапустите второй сервер. Запустите клиент, отправьте дейтаграмму и проверьте, что второй сервер захватил порт первого сервера. Если возможно, запустите второй сервер снова с учетной записью, отличной от учетной записи первого сервера, чтобы проверить, происходит ли по-прежнему захват порта, поскольку некоторые производители не допускают второго связывания, если идентификатор пользователя отличен от идентификатора процесса, уже связанного с портом.

9. В конце раздела 2.12 мы показали два примера Telnet: сервер времени и даты и эхо-сервер. Зная, что клиент проходит через два этапа – функцию gethostbynameи функцию connect, определите, к каким этапам относятся строки вывода клиента.

10. Функции getnameinfoможет потребоваться длительное время (до 80 с) на возвращение ошибки, если для IP-адреса не может быть найдено имя узла. Напишите новую функцию getnameinfo_timeo, которая получает дополнительный целочисленный аргумент, задающий максимальную длительность ожидания ответа в секундах. Если время таймера истекает и флаг NI_NAMEREQDне задан, вызовите функцию inet_ntopи возвратите строку адреса.

Часть 3
Дополнительные возможности сокетов

Глава 12
Совместимость IPv4 и IPv6
12.1. Введение

В течение ближайших лет, возможно, произойдет постепенный переход Интернета с IPv4 на IPv6. Во время этого переходного периода важно, чтобы существующие приложения IPv4 продолжали работать с более новыми приложениями IPv6. Например, производитель не может предложить клиент Telnet, работающий только с серверами IPv6, – он должен предоставить и клиент для серверов IPv4, и клиент для серверов IPv6. Мы бы предпочли обойтись одним Telnet-клиентом IPv6, способным работать с серверами и IPv4, и IPv6, и одним сервером Telnet, который работал бы с клиентами и IPv4, и IPv6. В этой главе мы увидим, как это сделать.

В этой главе мы предполагаем, что на узлах работают двойные стеки протоколов( dual stacks), то есть набор протоколов IPv4 и набор протоколов IPv6. На рис. 2.1 представлен узел с двойным стеком. Возможно, узлы и маршрутизаторы будут работать подобным образом в течение многих лет в процессе перехода к IPv6. В какой-то момент многие системы смогут отключить свои стеки IPv4, но только с течением времени можно будет сказать, когда это произойдет, да и произойдет ли вообще.

В этой главе мы обсудим, каким образом приложения IPv4 и IPv6 могут взаимодействовать друг с другом. Существует четыре комбинации клиентов и серверов, использующих либо IPv4, либо IPv6, что показано в табл. 12.1.

Таблица 12.1. Сочетания клиентов и серверов, использующих IPv4 или IPv6


Клиент IPv4 и серверыПочти все существующие клиентыОбсуждается в разделе 12.2
Клиент IPv6Обсуждается в разделе 12.3Простые модификации большинства существующих клиентов (например, клиент из листинга 1.1 модифицируется к виду, представленному в листинге 1.2)

Мы не будем подробно рассматривать два сценария, когда клиент и сервер используют один и тот же протокол. Более интересны случаи, когда клиент и сервер используют разные протоколы.

12.2. Клиент IPv4, сервер IPv6

Общим свойством узла с двойным стеком является то, что серверы IPv6 могут выполнять обслуживание клиентов IPv4 и IPv6. Это достигается за счет преобразования адресов IPv4 к виду IPv6 (см. рис. А.6). Пример такого преобразования приведен на рис. 12.1.

Рис. 12.1. Сервер IPv6 на узле с двойным стеком, обслуживающий клиенты IPv4 и IPv6

Слева у нас находятся клиент IPv4 и клиент IPv6. Сервер (справа) написан с использованием IPv6 и запущен на узле с двойным стеком. Сервер создал прослушиваемый TCP-сокет IPv6, связанный с универсальным адресом IPv6, и порт TCP 9999.

Мы считаем, что клиент и сервер находятся в одной сети Ethernet. Они могут быть соединены и через маршрутизаторы, поскольку все маршрутизаторы поддерживают и IPv4, и IPv6, но в данном случае это ничего не меняет. В разделе Б.3 описывается другой случай, когда клиенты и серверы IPv6 соединяются через маршрутизаторы, поддерживающие только IPv4.

Мы считаем, что оба клиента посылают сегменты SYN для установления соединения с сервером. Узел клиента IPv4 посылает сегмент SYN и дейтаграмму IPv4, а клиент IPv6 посылает сегмент SYN и дейтаграмму IPv6. Сегмент TCP от клиента IPv4 выглядит в сети как заголовок Ethernet, за которым идет заголовок IPv4, заголовок TCP и данные TCP. Заголовок Ethernet содержит поле типа 0x0800, которое идентифицирует кадр как кадр IPv4. Заголовок TCP содержит порт получателя 9999 (в приложении А рассказывается более подробно о форматах и содержании этих заголовков). IP-адрес получателя в заголовке IPv4, который мы не показываем, – это 206.62.226.42.

Сегмент TCP от клиента IPv6 выглядит в сети как заголовок Ethernet, за которым следует заголовок IPv6, заголовок TCP и данные TCP. Заголовок Ethernet содержит поле типа 0x86dd, которое идентифицирует кадр как кадр IPv6. Заголовок TCP имеет тот же формат, что и заголовок TCP в пакете IPv4, и содержит порт получателя 9999. IP-адрес получателя в заголовке IPv6, который мы не показываем, будет таким: 5f1b:df00:ce3e:e200:20:800:2b37:6426.

Принимающий канальный уровень просматривает поле типа Ethernet и передает каждый кадр соответствующему модулю IP. Модуль IPv4 (возможно, вместе с модулем TCP) определяет, что сокетом получателя является сокет IPv6, и IPv4-адрес отправителя в заголовке IPv4 заменяется на эквивалентный ему адрес IPv4, преобразованный к виду IPv6. Этот преобразованный адрес возвращается сокету IPv6 как IPv6-адрес клиента, когда функция acceptсервера соединяется с клиентом IPv4. Все оставшиеся дейтаграммы для этого соединения являются дейтаграммами IPv4.

Когда функция сервера accept соединяется с клиентом IPv6, клиентский адрес IPv6 остается таким же, каким был адрес отправителя в заголовке IPv6. Все оставшиеся дейтаграммы для этого соединения являются дейтаграммами IPv6.

Теперь мы можем свести воедино шаги, позволяющие TCP-клиенту IPv4 соединяться с сервером IPv6.

1. Сервер IPv6 запускается, создает прослушиваемый сокет IPv6, и мы считаем, что с помощью функции bindон связывает с сокетом универсальный адрес.

2. Клиент IPv4 вызывает функцию gethostbynameи находит запись типа А для сервера. У узла сервера будут записи и типа А, и типа AAAA, поскольку он поддерживает оба протокола, но клиент IPv4 запрашивает только запись типа А.

3. Клиент вызывает функцию connect, и клиентский узел отправляет серверу сегмент SYN IPv4.

4. Узел сервера получает сегмент SYN IPv4, направленный прослушиваемому сокету IPv6, устанавливает флаг, указывающий, что это соединение использует адреса IPv4, преобразованные к виду IPv6, и отвечает сегментом IPv4 SYN/ACK. Когда соединение установлено, адрес, возвращаемый серверу функцией accept, является адресом IPv4, преобразованным к виду IPv6.

5. Все взаимодействие между клиентом и сервером происходит с использованием дейтаграмм IPv4.

6. Пока сервер не определит при помощи явного запроса, является ли данный IPv6-адрес адресом IPv4, преобразованным к виду IPv6 (с использованием макроопределения IN6_IS_ADDR_V4MAPPED, описанного в разделе 10.4), он не будет знать, что взаимодействует с клиентом IPv4. Двойной стек протоколов решает эту проблему. Аналогично, клиент IPv4 не знает, что он взаимодействует с сервером IPv6.

Главное в данном сценарии то, что узел сервера с двойным стеком имеет и адрес IPv4, и адрес IPv6. Этот сценарий будет работать, пока используются адреса IPv4.

Сценарий работы UDP-сервера IPv6 аналогичен, но формат адреса может меняться для каждой дейтаграммы. Например, если сервер IPv6 получает дейтаграмму от клиента IPv4, адрес, возвращаемый функцией recvfrom, будет адресом IPv4, преобразованным к виду IPv6. Сервер отвечает на запрос клиента, вызывая функцию sendtoс адресом IPv4, преобразованным к виду IPv6, в качестве адреса получателя. Формат адреса сообщает ядру, что нужно отправить клиенту дейтаграмму IPv4. Но следующей дейтаграммой, полученной сервером, может быть дейтаграмма IPv6, и функция recvfromвозвратит адрес IPv6. Если сервер отвечает, ядро генерирует дейтаграмму IPv6.

На рис. 12.2 показано, как обрабатывается полученная дейтаграмма IPv4 или IPv6 в зависимости от типа принимающего сокета для TCP и UDP. Предполагается, что это узел с двойным стеком.

Рис. 12.2. Обработка полученных дейтаграмм IPv4 или IPv6 в зависимости от типа принимающего сокета

■ Если дейтаграмма IPv4 приходит на сокет IPv4, ничего особенного не происходит. На рисунке изображены две стрелки, помеченные «IPv4»: одна для TCP, другая для UDP. Между клиентом и сервером происходит обмен дейтаграммами IPv4.

■ Если дейтаграмма IPv6 приходит на сокет IPv6, ничего особенного не происходит. На рисунке изображены две стрелки, помеченные «IPv6»: одна для TCP. другая для UDP. Между клиентом и сервером происходит обмен дейтаграммами IPv6.

■ Когда дейтаграмма IPv4 приходит на сокет IPv6, ядро возвращает соответствующий адрес IPv4, преобразованный к виду IPv6, в качестве адреса, возвращаемого функцией accept (TCP) или recvfrom (UDP). На рисунке это показано двумя штриховыми стрелками. Такое сопоставление возможно, поскольку адрес IPv4 можно всегда представить как адрес IPv6. Между клиентом и сервером происходит обмен дейтаграммами IPv4.

■ Обратное неверно: поскольку, вообще говоря, адрес IPv6 нельзя представить как адрес IPv4, на рисунке отсутствуют стрелки от протокола IPv6 к двум сокетам IPv4.

Большинство узлов с двойным стеком должны использовать следующие правила обращения с прослушиваемыми сокетами:

1. Прослушиваемый сокет IPv4 может принимать соединения только от клиентов IPv4.

2. Если у сервера есть прослушиваемый сокет IPv6, связанный с универсальным адресом, и параметр сокета IPV6_V6ONLY (см. раздел 7.8) не установлен, этот сокет может принимать исходящие соединения как от клиентов IPv4, так и от клиентов IPv6. Для соединения с клиентом IPv4 локальный адрес сервера для соединения будет соответствующим адресом IPv4, преобразованным к виду IPv6.

3. Если у сервера есть прослушиваемый сокет IPv6, связанный с адресом IPv6, не являющимся адресом IPv4, преобразованным к виду IPv6, или его сокет связан с универсальным адресом при установленном параметре сокета IPV6_V6ONLY (раздел 7.8), этот сокет может принимать исходящие соединения только от клиентов IPv6.

12.3. Клиент IPv6, сервер IPv4

Теперь мы поменяем протоколы, используемые клиентом и сервером в примере из предыдущего раздела. Сначала рассмотрим TCP-клиент IPv6, запущенный на узле с двойным стеком протоколов.

1. Сервер IPv4 запускается на узле, поддерживающем только IPv4, и создает прослушиваемый сокет IPv4.

2. Запускается клиент IPv6 и вызывает функцию gethostbyname, запрашивая только адреса IPv6 (запрашивает семейство AF_INET6и устанавливает флаг AI_V4MAPPEDв структуре hints). Поскольку у сервера, поддерживающего только IPv4, есть лишь записи типа А, мы видим, согласно табл. 11.3, что клиенту возвращается адрес IPv4, преобразованный к виду IPv6.

3. Клиент IPv6 вызывает функцию connect с адресом IPv4, преобразованным к виду IPv6, в структуре адреса сокета IPv6. Ядро обнаруживает преобразованный адрес и автоматически посылает серверу сегмент SYN IPv4.

4. Сервер отвечает сегментом SYN/ACK IPv4, и устанавливается соединение, по которому происходит обмен дейтаграммами IPv4. Этот сценарий мы схематически изображаем на рис. 12.3.

Рис. 12.3. Обработка клиентских запросов в зависимости от типа адреса и типа сокета

■ Если TCP-клиент IPv4 вызывает функцию connect, задавая адрес IPv4, или если UDP-клиент IPv4 вызывает функцию sendto, задавая адрес IPv4, ничего особенного не происходит. На рисунке это изображено двумя стрелками, помеченными «IPv4».

■ Если TCP-клиент IPv6 вызывает функцию connect, задавая адрес IPv6, или если UDP-клиент IPv6 вызывает функцию sendto, задавая адрес IPv6, тоже ничего особенного не происходит. На рисунке это показано двумя стрелками, помеченными «IPv6».

■ Если TCP-клиент IPv6 вызывает функцию connect, задавая адрес IPv4, преобразованный к виду IPv6, или если UDP-клиент вызывает функцию sendto, задавая адрес IPv4, преобразованный к виду IPv6, ядро обнаруживает сопоставленный адрес и инициирует отправку дейтаграммы IPv4 вместо дейтаграммы IPv6. На рисунке это показано двумя штриховыми стрелками.

■ Клиент IPv4 не может задать адрес IPv6 ни функции connect, ни функции sendto, поскольку 16-байтовый адрес IPv6 не соответствует 4-байтовой структуре in_addrв структуре IPv4 sockaddr_in. Следовательно, на рисунке нет стрелок от сокетов IPv4 к протоколу IPv6.

В предыдущем разделе (дейтаграмма IPv4, приходящая для сокета сервера IPv6) преобразование полученного адреса IPv4 к виду IPv6 выполняется ядром и результат прозрачно (то есть незаметно для приложения) возвращается приложению функцией acceptили recvfrom. В этом разделе (если необходимо отправить дейтаграмму IPv4 на сокете IPv6) преобразование адреса IPv4 к виду IPv6 выполняется распознавателем в соответствии с правилами, представленными в табл. 11.3, и затем преобразованный адрес прозрачно передается приложению функцией connectили sendto.

Резюме: совместимость IPv4 и IPv6

Таблица 12.2, содержащая сочетания клиентов и серверов, подводит итог обсуждению, проведенному в данном и предыдущем разделах.

Таблица 12.2. Обобщение совместимости клиентов и серверов IPv4 и IPv6


Клиент IPv4, узел только IPv4IPv4НетIPv4IPv4
Клиент IPv6, узел только IPv6НетIPv6НетIPv6
Клиент IPv4, узел с двойным стекомIPv4НетIPv4IPv4
Клиент IPv6, узел с двойным стекомIPv4IPv6Нет*IPv6

Каждая ячейка этой таблицы содержит поля «IPv4» или «IPv6» с указанием используемого протокола, если данное сочетание работает, либо «нет», если комбинация недопустима. Ячейка в последней строке третьей колонки отмечена звездочкой, поскольку совместимость зависит от адреса, выбранного клиентом. При выборе записи типа AAAA отправка дейтаграммы IPv6 будет невозможна. Но выбор записи типа А, которая возвращается клиенту как адрес IPv4, преобразованный к виду IPv6, приведет к отправке дейтаграммы IPv4. Перебрав все адреса, возвращаемые getaddrinfo, мы обязательно доберемся до адреса IPv4, преобразованного к виду IPv6, пусть даже и потратив некоторое время на безуспешное ожидание.

Хотя четверть из представленных в таблице сочетаний недопустима, в обозримом будущем большинство реализаций IPv6 будут использоваться на узлах с двойным стеком протоколов и поддерживать не только IPv6. Если мы удалим из таблицы вторую строку и вторую колонку, все записи «Нет» исчезнут и единственной проблемой останется запись, помеченная звездочкой.

12.4. Макроопределения проверки адреса IPv6

Существует небольшой класс приложений IPv6, которые должны знать, с каким собеседником они взаимодействуют (IPv4 или IPv6). Эти приложения должны знать, является ли адрес собеседника адресом IPv4, преобразованным к виду IPv6. Определены двенадцать макросов, проверяющих некоторые свойства адреса Ipv6.

#include

int IN6_IS_ADDR_UNSPECIFIED(const struct in6_addr * aptr);

int IN6_IS_ADDR_LOOPBACK(const struct in6_addr * aptr);

int IN6_IS_ADDR_MULTICAST(const struct in6_addr * aptr);

int IN6_IS_ADDR_LINKLOCAL(const struct in6_addr * aptr);

int IN6_IS_ADDR_SITELOCAL(const struct in6_addr * aptr);

int IN6_IS_ADDR_V4MAPPED(const struct in6_addr * aptr);

int IN6_IS_ADDR_V4COMPAT(const struct in6_addr * aptr);

int IN6_IS_ADDR_MC_NODELOCAL(const struct in6_addr * aptr);

int IN6_IS_ADDR_MC_LINKLOCAL(const struct in6_addr * aptr);

int IN6_IS_ADDR_MC_SITELOCAL(const struct in6_addr * aptr);

int IN6_IS_ADDR_MC_ORGLOCAL(const struct in6_addr * aptr);

int IN6_IS_ADDR_MC_GLOBAL(const struct in6_addr * aptr);

Все возвращают: ненулевое значение, если адрес IPv6 имеет указанный тип, 0 в противном случае

Первые семь макросов проверяют базовый тип адреса IPv6. Мы покажем различные типы адресов в разделе А.5. Последние пять макросов проверяют область действия адреса многоадресной передачи IPv6 (см. раздел 19.2).

Клиент IPv6 может вызвать макрос IN6_IS_ADDR_V4MAPPEDдля проверки адреса IPv6, возвращенного распознавателем. Сервер IPv6 может вызвать этот макрос для проверки адреса IPv6, возвращенного функцией accept или recvfrom.

Как пример приложения, которому нужен этот макрос, можно привести FTP и его команду PORT. Если мы запустим FTP-клиент, зарегистрируемся на FTP-сервере и выполним команду FTP dir, FTP-клиент пошлет команду PORTFTP-серверу через управляющее соединение. Она сообщит серверу IP-адрес и порт клиента, с которым затем сервер создаст соединение. (В главе 27 [111] содержатся подробные сведения о протоколе приложения FTP.) Но FTP-клиент IPv6 должен знать, с каким сервером имеет дело – IPv4 или IPv6, поскольку сервер IPv4 требует команду в формате PORT a1, a2, a3, a4, p1, p2(где первые четыре числа, каждое от 0 до 255, формируют 4-байтовый адрес IPv4, а два последних – 2-байтовый номер порта), а серверу IPv6 необходима команда EPRT(RFC 2428 [3]), содержащая семейство адреса, адрес в текстовом формате и порт в текстовом формате. В упражнении 12.1 приводятся примеры использования обеих команд.


    Ваша оценка произведения:

Популярные книги за неделю