, задавая адрес 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, узел только IPv4 (только А)
Сервер IPv4, узел только IPv6 (только AAAA)
Сервер IPv4, узел с двойным стеком (А и AAAA)
Сервер IPv6, узел с двойным стеком (А и AAAA)
Клиент IPv4, узел только IPv4
IPv4
Нет
IPv4
IPv4
Клиент IPv6, узел только IPv6
Нет
IPv6
Нет
IPv6
Клиент IPv4, узел с двойным стеком
IPv4
Нет
IPv4
IPv4
Клиент IPv6, узел с двойным стеком
IPv4
IPv6
Нет*
IPv6
Каждая ячейка этой таблицы содержит поля «IPv4» или «IPv6» с указанием используемого протокола, если данное сочетание работает, либо «нет», если комбинация недопустима. Ячейка в последней строке третьей колонки отмечена звездочкой, поскольку совместимость зависит от адреса, выбранного клиентом. При выборе записи типа AAAA отправка дейтаграммы IPv6 будет невозможна. Но выбор записи типа А, которая возвращается клиенту как адрес IPv4, преобразованный к виду IPv6, приведет к отправке дейтаграммы IPv4. Перебрав все адреса, возвращаемые
getaddrinfo
, мы обязательно доберемся до адреса IPv4, преобразованного к виду IPv6, пусть даже и потратив некоторое время на безуспешное ожидание.
Хотя четверть из представленных в таблице сочетаний недопустима, в обозримом будущем большинство реализаций IPv6 будут использоваться на узлах с двойным стеком протоколов и поддерживать не только IPv6. Если мы удалим из таблицы вторую строку и вторую колонку, все записи «Нет» исчезнут и единственной проблемой останется запись, помеченная звездочкой.
12.4. Макроопределения проверки адреса IPv6
Существует небольшой класс приложений IPv6, которые должны знать, с каким собеседником они взаимодействуют (IPv4 или IPv6). Эти приложения должны знать, является ли адрес собеседника адресом IPv4, преобразованным к виду IPv6. Определены двенадцать макросов, проверяющих некоторые свойства адреса Ipv6.
#include <netinet/in.h>
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-клиент пошлет команду
PORT
FTP-серверу через управляющее соединение. Она сообщит серверу 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 приводятся примеры использования обеих команд.
12.5. Переносимость исходного кода
Большинство существующих сетевых приложений написаны для IPv4. Структуры
sockaddr_in
размещаются в памяти и заполняются, а функция
socket
задает
AF_INET
в качестве первого аргумента. При переходе от листинга 1.1 к листингу 1.2 мы видели, что эти приложения IPv4 можно преобразовать в приложения IPv6 без особых усилий. Многие показанные нами изменения можно выполнить автоматически, используя некоторые сценарии редактирования. Программы, более зависящие от IPv4, использующие такие свойства, как многоадресная передача, параметры IP или символьные (неструктурированные) сокеты, потребуют больших усилий при преобразовании.
Если мы преобразуем приложение для работы с IPv6 и распространим его исходный код, нам придется думать о том, поддерживает ли принимающая система протокол IPv6. Типичный способ решения этой проблемы — применять в коде
#ifdef
, используя по возможности IPv6 (поскольку мы видели в этой главе, что клиент IPv6 может взаимодействовать с серверами IPv4 и наоборот). Проблема такого подхода в том, что код очень быстро засоряется директивами
#ifdef
, и его становится сложнее отслеживать и обслуживать.