Преобразование приложений C++ TCP/IP из IPv4 в IPv6. Сложно? Стоит хлопот?
За прошедшие годы я разработал небольшую массу серверных / клиентских приложений на C++ для Windows с использованием WinSock (маршрутизаторы, веб-серверы, почтовые /FTP-серверы и т. Д. И т. Д.).
Я начинаю все больше думать о создании версии этих приложений для IPv6 (конечно, при сохранении и оригинальной версии IPv4).
Вопросы:
- С какими подводными камнями я могу столкнуться?
- Сложно ли портировать / конвертировать?
- Стоит ли преобразование?
Для справки (или для удовольствия) вы можете подсмотреть пик кода IPv4 в основе моих приложений.
4 ответа
getaddrinfo и getnameinfo ваши друзья. Насколько я могу предположить, они будут вашими лучшими друзьями в вашем стремлении обеспечить поддержку IPv4 и IPv6 в существующем приложении.
Если все сделано правильно, добавив поддержку IPv6, вы также в конечном итоге абстрагируете систему до такой степени, что неизвестный будущий протокол IP может работать без модификации кода.
Обычно при подключении вы должны указать структуру сокета, порт, семейство адресов, IP-адрес, преобразование адреса / портов в сетевой порядок байтов и т. Д.
С getaddrinfo
Вы отправляете IP-адрес или имя хоста и порт или имя порта, и он возвращает связанный список со структурами и всем готовым для передачи непосредственно в socket()
а также connect()
,
getaddrinfo
имеет решающее значение для работы с обоими протоколами IP, поскольку он знает, имеет ли хост подключение к IPv6 или IPv4, и он знает, делает ли узел также, глядя на DNS AAAA
против A
записывает и динамически определяет, какие протоколы доступны для обслуживания определенного запроса на соединение.
Я настоятельно рекомендую против использования inet_pton()
, inet_addr()
или знакомые устройства, которые зависят от версии IP. На платформе Windows специально inet_pton()
не совместим с более ранними версиями MS Windows (XP, 2003 и др.), если вы не свернули свою собственную. Также советуем не использовать отдельные версии для IPv4 и IPv6... Это нереально как техническое решение, поскольку в ближайшем будущем оба протокола необходимо будет использовать одновременно, и люди могут заранее не знать, какой именно использовать. Интерфейсы сокетов абстрактны, и легко обнаружить поддержку двух стеков или IPv6, пытаясь создать сокет IPv6 или установить параметр сокета IPv6 для двух стеков для слушателей. Нет причин, по которым полученное приложение не будет работать в системе, которая не поддерживает или не знает о IPv6.
Для исходящих соединений используйте PF_UNSPEC
в getaddrinfo
так что семейство адресов выбирается для вас при установлении исходящих соединений. Это, IMHO, лучше, чем подход с двумя стеками, поскольку он позволяет платформам, которые не поддерживают работу с двумя стеками, работать.
Для входящих соединений вы можете либо связывать сокеты IPv4/IPv6 отдельно, если это разумно, учитывая дизайн, или использовать двойной стек, если вы не можете использовать отдельных слушателей. При использовании двойного стека getnameinfo
возвращает IPv6-адрес для IPv4-адресов, который, по-моему, оказывается бесполезным. Небольшая служебная программа может преобразовать строку в обычный адрес IPv4.
По моему опыту, когда все сделано правильно, вы удалили зависимости от определенных версий IP и получили меньше кода управления сокетами, чем вы начали.
Около года назад я добавил поддержку IPv6 в свою сетевую библиотеку, поддерживающую только IPv4, и мне это не показалось ужасно трудным или травмирующим.
Единственная большая разница в том, как вы храните IP-адреса:
В IPv4 вы храните их как sockaddr_in
(или, если вы непослушный, как я, как uint32_t).
Для IPv6 их нужно хранить как sockaddr_in6
's (или некоторая эквивалентная 128-битная структура).
Хорошим шагом перед преобразованием было бы пройтись по коду и найти все места, где в данный момент хранятся адреса IPv4, и абстрагировать их в общий класс IP-адресов, который впоследствии может быть переопределен внутри, чтобы быть либо адресом IPv4, либо Адрес IPv6.
Затем повторите тестирование, чтобы убедиться, что в режиме IPv4 ничего не сломано... как только это будет проверено, вы сможете переключиться на IPv6 с помощью всего лишь нескольких изменений (в основном меняющихся PF_INET
в PF_INET6
, inet_aton()
в inet_pton()
, так далее...).
Моя библиотека по умолчанию по-прежнему поставляется только для IPv4, но с возможностью определения макроса препроцессора (-DMUSCLE_USE_IPV6
) перекомпилировать его в режиме с поддержкой IPv6.
Таким образом, он все еще может быть скомпилирован на системах, которые не поддерживают IPv6. Одна очень полезная функция, которую я нашел на этом пути, - это IPv4-сопоставленные адреса IPv6: указав один из них (по сути, адрес IPv4 с 0xFFFF
в дополнение к этому), вы получите сокет, который может обмениваться данными как с IPv4, так и с IPv6, и, таким образом, сервер, который может обмениваться данными как с клиентами IPv4, так и с клиентами IPv6 одновременно, без необходимости писать отдельные пути кода IPv4 и IPv6 для всего.
Что касается того, стоит ли это усилий, это действительно зависит от того, что вы собираетесь делать с кодом. Я бы сказал, что это хороший образовательный опыт, если не сказать ничего другого, и он позволяет использовать ваше программное обеспечение в средах IPv6, которые со временем станут более распространенными.
Ульрих Дреппер, сопровождающий glibc, написал хорошую статью на эту тему:
http://people.redhat.com/drepper/userapi-ipv6.html
Но не забывайте книгу Ричарда Стивена " Сетевое программирование Unix", том 1: API-интерфейс для работы с сокетами.
Посмотрите журналы изменений некоторых проектов с открытым исходным кодом, в которых реализован IPv6. В основном это код Unix, но Winsock очень похож на сокеты BSD.
Exim, Courier, Squid, Apache, BIND DNS - вот несколько мест, где можно начать поиск.