Через некоторое время после того, как у меня появился мой первый модем, мне говорили, что Интернет-протокол скоро будет заменен на новый и гораздо более лучший. Человек, объясняющий это, в действительности не знал, чем же он будет лучше, но был уверен, что он будет отличным и он станет повсеместно используемым в ближайшее время. Почти два десятилетия спустя, мы все еще, в основном, используем IPv4 — все тот же протокол, который я использовал на моем первом модеме.
Новый протокол, который зовется IPv6, в настоящее время окончательно утвержден, и поддерживается большинством современных операционных систем, но все равно еще широко не используется. В этой статье я рассмотрю некоторые преимущества, которые он предоставляет и как обеспечить его поддержку в своих приложениях.
Наиболее рекламируемой особенностью IPv6 является большее адресное пространство. Если вы что-нибудь читали о IPv6, то вы, наверное, знаете, что он увеличивает размер адреса с 32 бит до 128 бит. Этого более чем достаточно для того, чтобы каждый когда-либо рожденный человек имел частную сеть, большую, чем нынешний Интернет. Даже если все, что у вас есть (в том числе и вещи, которые не содержат какой-либо электроники) будет иметь свой собственный адрес IPv6, то вы все равно будете использовать только небольшую часть адресного пространства.
Это очень важно, потому что это может упростить маршрутизацию. Маршрутизаторы обычно соединяют относительно небольшое количество сетей. Простейший пример — ваш домашний маршрутизатор, который соединяет вашу локальную сеть с Интернетом. Для каждого пакета, который он получает, он должен проделать одно из трех действий: отбросить его, перенаправить во внутреннюю сеть, или перенаправить во внешнюю сеть.
Для типичной домашней сети это очень простое решение. Если адрес получателя находится в одном из зарезервированных внутренних диапазонов — отправить его внутрь, в противном случае отправить его наружу. Большие коммерческие маршрутизаторы должны принимать гораздо более сложные решения. С середины 90-х, когда адреса IPv4 начали рассматривать как ограниченный ресурс, они были поделены на 8-битные диапазоны. Это означает, что вы можете получить три смежных блока в совершенно разных сетях. При такой схеме выделения получается 2^24 возможных сетей, и маршрутизатор должен быть в состоянии решать, через какое соединение нужно отправить пакет, предназначенный для любой из них. 2^24 это чуть меньше 17 миллионов. К счастью, для упрощения вы можете объединять их узлы, но это по-прежнему не упрощает принятие решения о маршрутах.
В IPv6 теперь есть достаточно адресов, чтобы каждой стране или крупной сети можно было бы выделить широкий диапазон. Затем можно выделить поддиапазоны для подключенных сетей и так далее. Это иерархическое назначение (по крайней мере, теоретически) упрощает маршрутизацию.
Одна из основных жалоб на IPv6 исходит от людей, которые думают, что NAT является средством безопасности, и путают «маршрутизируемый» и «доступный». В IPv4, большинство домашних пользователей (и почти все мобильные пользователи) используют трансляцию сетевых адресов (NAT). Ваш компьютер имеет частный IP адрес, а маршрутизатор имеет публичный адрес. Каждый соединенный порт на вашем частном IP отображается на порт на публичный IP адрес. Это не обеспечивает никакой безопасности. Большинство реализаций NAT также по умолчанию отвергают соединения, инициируемые извне, в то время как некоторые реализации перенаправляют соединения к назначенному по умолчанию узлу.
Политика отвергания инициируемых извне соединений обеспечивает безопасность, но это обеспечивается файерволом маршрутизатора и не является свойственным для NAT. Большинство не-NAT файерволов будет делать то же самое.
То, что ваш компьютер имеет внешние маршрутизируемые адреса IPv6, вовсе не означает, что он доступен извне. Межсетевой экран, через который вы подключены к Интернету, по-прежнему определяет политики, согласно которым можно подключиться. Учитывая количество хаков используемых для проникновения через NAT чтобы заставить работать такие вещи, как например VoIP. Просто удивительно, что еще кто-то до сих пор думает, что NAT добавляет безопасности, но, видимо, некоторые так считают.
Некоторые изменения в IPv6 относительно просты, и нужны для того, чтобы просто сделать обязательными те вещи, которые не являются обязательными частями IPv4. Одной из наиболее интересных вещей является IPsec. В настоящее время он используется в основном для VPN, создания шифрованного соединения между двумя маршрутизаторами и предотвращения перехвата любых промежуточных пакетов.
Используя IPv6, вы можете быть уверены, что любая конечная точка будет поддерживать IPsec, что означает, что вы всегда можете установить шифрованное соединение. В IPv4, в основном, вы будете использовать SSL для шифрования. Это происходит на несколько более высоком уровне в стеке протоколов и требует, чтобы каждое приложение, которое использует шифрование, должно быть специально настроено для этого.
В настоящее время, IPsec наиболее часто используется с ключами шифрования, которые являются общими для группы. Одним из альтернативных способов использования является внедрение открытого ключа, который может быть использован для установления IPsec соединения, в записи DNS. Чтобы это работало, запись DNS сама нуждается в подписании при помощи DNSSEC.
Если вы делаете ваши DNS запросы через IPsec, то вы имеете шифрованный запрос и ответ, поэтому никто не может сказать, какие сайты вы ищете (хотя, очевидно, ваш провайдер знает, на какие IP адреса вы подключались). В отличие от SSL, IPsec работает для всех видов соединений, в том числе UDP, так что вещи типа VoIP могут получать значительные преимущества от использования IPv6. Конечные точки можно соединить напрямую, без необходимости использования NAT, и все соединения могут быть зашифрованы.
Другим дополнением является групповая передача (multicast). В IPv4, один адрес в каждой подсети резервируется как широковещательный адрес. Пакеты, отправленные на этот адрес, доставляются каждому компьютеру в подсети. Это имело большой смысл, когда большинство сетей были общей шиной. Все пакеты отправлялись в любом случае, это только говорило каждому получателю, чтобы он принимал их. Посылка на широковещательный адрес была более эффективной, чем посылка двух копий пакета, если два человека хотели получить его.
Для коммутируемых сетей это не тот случай. Каждый компьютер может отправлять и получать определенное количество данных, и коммутатор будет распределять эти пакеты между машинами. Так, 4 компьютера в сети 100 Mb/s могут иметь 2 независимых передачи на 100Mb/s. Если вы отправляете пакет на широковещательный адрес, он рассылается всем, даже тем, кто не хочет его получать.
Групповая передача немного умнее. Она определяет группы компьютеров и назначает им общий IP адрес. Пакеты, отсылаемые на этот адрес, направляются на каждый компьютер, который решил войти в группу. В отличие от широковещательной передачи, групповая передача маршрутизируема. Вы можете иметь множество компьютеров в различных сетях в multicast-группе, и генерировать только два пакета, если исходный пакет достигает маршрутизатора, за которым имеются участники, состоящие в двух нижестоящих сетях. Если, к примеру, ваша интернет-радиостанция использует групповую передачу IPv6, то радиостанция будет передавать один поток пакетов вашему провайдеру. Ваш провайдер должен будет отправить копию потока для каждого из своих клиентов, которые слушают эту станцию. Когда пакеты попадут в ваш маршрутизатор, он отправит копию каждой машине в вашей сети, которая слушает этот поток.
Вы можете использовать тот же механизм для таких вещей, как конференц-звонки. В настоящее время, если у вас есть 10 человек в сессии видеоконференции, то либо каждый должен выдавать 10 копий потока со своей камеры, либо вам необходимо иметь где-то сервер, который сможет справиться с ретрансляцией. При групповой рассылке, все отправляли бы один экземпляр для 10 человек. В потребительских сетях, которые обычно имеют канал на прием гораздо шире, чем на отдачу, это особенно привлекательно.
Широковещательная трансляция также используется, когда вы не знаете, например, какой компьютер вы должны использовать для обнаружения сервисов. IPv6 заменяет этот способ при помощи anycast. Anycast — это нечто, похожее на multicast в группе адресов, ассоциированных с anycast-адресом, но в отличие от multicast, пакеты, отправленные на этот адрес доставляются только на одну машину. Это полезно для таких вещей, как, например, автоконфигурация.
Надеюсь, что теперь вы можете видеть, что поддержка IPv6 является полезной. Тогда возникает вопрос — как? Большинство сетевого кода либо использует сокеты Беркли или высокоуровневый API, построенный на них. Если вы используете высокоуровневый API, то вы, вероятно, уже имеете поддержку IPv6, предоставляемую кем-то другим. Если вы используете сокеты, то вам необходимо внести небольшие изменения в код.
API сокетов Беркли был основан на UNIX идее, что все есть файл. Вы общаетесь с удаленными машинами, используя дескрипторы с некоторыми дополнительными особенностями. Если у вас есть сокет, то API полностью протоколо-независимый. Используете ли вы IPv4, IPv6, AppleTalk, или какой-либо другой протокол, код будет одинаковый.
К сожалению, создание сокетов — это то место, где начинаются проблемы. Вы создаете сокет при помощи такой функции:
Три параметра полностью определяют некоторые аспекты протокола. В большинстве случаев в конечном итоге вы жестко задаете domain как PF_INET, подразумевая IPv4. Все становится еще хуже, когда вы связываете сокет с локальным адресом, а затем или подключаетесь, или принимаете подключения. Все соответствующие функции принимают указатель на структуру sockaddr в качестве аргумента.
Эта структура довольно проста, но вы никогда не используете ее напрямую. Вместо этого вы используете что-то вроде структуры sockaddr_in, которая начинается так же, как структура sockaddr (с размером и семейством адреса), но содержит IPv4-адрес и номер порта. Вы обычно используете gethostbyname(), чтобы определить эти параметры.
Если вы хотите поддерживать еще один протокол, то вам необходимо добавить другую ветвь кода с другим механизмом определения. Одним из последних усовершенствований в версии POSIX от 2004 года является определение функции getaddrinfo(). Она определяет сервер, заданный строковым описанием адреса и сервиса. Это означает, что вам не придется жестко задавать номера портов. Использовать ее довольно просто. Этот код будет подключаться к серверу InformIT по HTTP, используя любые доступные соединения:
Первые два аргумента getaddrinfo() являются именем сервера и именем протокола. Преобразователь найдет сочетание адреса сервера и сервиса. На некоторых платформах, таких как OS X, это даже включает в себя определение записи DNS SRV, так что эта операция настроит порт правильно, даже если сервер работает на нестандартном порту, пока запись DNS содержит этот факт. Структура hints используется для задания некоторых ограничений на возвращаемую информацию об адресе. В этом случае, мы хотим только поточные сокеты, и мы готовы принять их в абсолютно любом протоколе, поддерживаемом сетевым стеком. Последний аргумент является указателем, который используется для возврата массива структур информации об адресах.
Обратите внимание, что все аргументы в socket() и connect() целиком являются данными об адресах, возвращенными вызовом функции getaddrinfo(). Ничто из этого кода вообще не говорит о том, используете ли вы IPv4, IPv6, или какой-либо другой новый протокол. До тех пор, пока ваша операционная система поддерживает протокол, этот код можно использовать.
Надеюсь, что в этой статье вы увидели, что IPv6 предоставляет некоторые захватывающие особенности, которые делают его заслуживающим поддержки. И, самое главное, что поддержка его стоит не так уж много усилий. Нет никаких оправданий для написания нового кода сокетов, который не поддерживает IPv6. И так же довольно просто обновление старого кода для его поддержки.
Я должен был рассмотреть функцию getaddrinfo(), когда я писал эту статью, потому что я обернул ее в класс, который берет имена хоста и протокола в качестве аргументов и через некоторое время возвращает соединенный сокет, и сейчас я просто использую этот класс во всем сетевом коде.
Свободных IPv4 адресов становится все меньше и меньше, и похоже, что все больше провайдеров используют NAT для всех своих клиентов. В какой-то момент устанавливать соединения end-to-end друг с другом напрямую смогут только те, кто использует приложения, которые поддерживают протокол IPv6 «из коробки». Если ваш код до сих пор не может этого, сейчас самое время исправить его.
Новый протокол, который зовется IPv6, в настоящее время окончательно утвержден, и поддерживается большинством современных операционных систем, но все равно еще широко не используется. В этой статье я рассмотрю некоторые преимущества, которые он предоставляет и как обеспечить его поддержку в своих приложениях.
Суть IPv6
Наиболее рекламируемой особенностью IPv6 является большее адресное пространство. Если вы что-нибудь читали о IPv6, то вы, наверное, знаете, что он увеличивает размер адреса с 32 бит до 128 бит. Этого более чем достаточно для того, чтобы каждый когда-либо рожденный человек имел частную сеть, большую, чем нынешний Интернет. Даже если все, что у вас есть (в том числе и вещи, которые не содержат какой-либо электроники) будет иметь свой собственный адрес IPv6, то вы все равно будете использовать только небольшую часть адресного пространства.
Это очень важно, потому что это может упростить маршрутизацию. Маршрутизаторы обычно соединяют относительно небольшое количество сетей. Простейший пример — ваш домашний маршрутизатор, который соединяет вашу локальную сеть с Интернетом. Для каждого пакета, который он получает, он должен проделать одно из трех действий: отбросить его, перенаправить во внутреннюю сеть, или перенаправить во внешнюю сеть.
Для типичной домашней сети это очень простое решение. Если адрес получателя находится в одном из зарезервированных внутренних диапазонов — отправить его внутрь, в противном случае отправить его наружу. Большие коммерческие маршрутизаторы должны принимать гораздо более сложные решения. С середины 90-х, когда адреса IPv4 начали рассматривать как ограниченный ресурс, они были поделены на 8-битные диапазоны. Это означает, что вы можете получить три смежных блока в совершенно разных сетях. При такой схеме выделения получается 2^24 возможных сетей, и маршрутизатор должен быть в состоянии решать, через какое соединение нужно отправить пакет, предназначенный для любой из них. 2^24 это чуть меньше 17 миллионов. К счастью, для упрощения вы можете объединять их узлы, но это по-прежнему не упрощает принятие решения о маршрутах.
В IPv6 теперь есть достаточно адресов, чтобы каждой стране или крупной сети можно было бы выделить широкий диапазон. Затем можно выделить поддиапазоны для подключенных сетей и так далее. Это иерархическое назначение (по крайней мере, теоретически) упрощает маршрутизацию.
Одна из основных жалоб на IPv6 исходит от людей, которые думают, что NAT является средством безопасности, и путают «маршрутизируемый» и «доступный». В IPv4, большинство домашних пользователей (и почти все мобильные пользователи) используют трансляцию сетевых адресов (NAT). Ваш компьютер имеет частный IP адрес, а маршрутизатор имеет публичный адрес. Каждый соединенный порт на вашем частном IP отображается на порт на публичный IP адрес. Это не обеспечивает никакой безопасности. Большинство реализаций NAT также по умолчанию отвергают соединения, инициируемые извне, в то время как некоторые реализации перенаправляют соединения к назначенному по умолчанию узлу.
Политика отвергания инициируемых извне соединений обеспечивает безопасность, но это обеспечивается файерволом маршрутизатора и не является свойственным для NAT. Большинство не-NAT файерволов будет делать то же самое.
То, что ваш компьютер имеет внешние маршрутизируемые адреса IPv6, вовсе не означает, что он доступен извне. Межсетевой экран, через который вы подключены к Интернету, по-прежнему определяет политики, согласно которым можно подключиться. Учитывая количество хаков используемых для проникновения через NAT чтобы заставить работать такие вещи, как например VoIP. Просто удивительно, что еще кто-то до сих пор думает, что NAT добавляет безопасности, но, видимо, некоторые так считают.
Безопасность по умолчанию
Некоторые изменения в IPv6 относительно просты, и нужны для того, чтобы просто сделать обязательными те вещи, которые не являются обязательными частями IPv4. Одной из наиболее интересных вещей является IPsec. В настоящее время он используется в основном для VPN, создания шифрованного соединения между двумя маршрутизаторами и предотвращения перехвата любых промежуточных пакетов.
Используя IPv6, вы можете быть уверены, что любая конечная точка будет поддерживать IPsec, что означает, что вы всегда можете установить шифрованное соединение. В IPv4, в основном, вы будете использовать SSL для шифрования. Это происходит на несколько более высоком уровне в стеке протоколов и требует, чтобы каждое приложение, которое использует шифрование, должно быть специально настроено для этого.
В настоящее время, IPsec наиболее часто используется с ключами шифрования, которые являются общими для группы. Одним из альтернативных способов использования является внедрение открытого ключа, который может быть использован для установления IPsec соединения, в записи DNS. Чтобы это работало, запись DNS сама нуждается в подписании при помощи DNSSEC.
Если вы делаете ваши DNS запросы через IPsec, то вы имеете шифрованный запрос и ответ, поэтому никто не может сказать, какие сайты вы ищете (хотя, очевидно, ваш провайдер знает, на какие IP адреса вы подключались). В отличие от SSL, IPsec работает для всех видов соединений, в том числе UDP, так что вещи типа VoIP могут получать значительные преимущества от использования IPv6. Конечные точки можно соединить напрямую, без необходимости использования NAT, и все соединения могут быть зашифрованы.
Другим дополнением является групповая передача (multicast). В IPv4, один адрес в каждой подсети резервируется как широковещательный адрес. Пакеты, отправленные на этот адрес, доставляются каждому компьютеру в подсети. Это имело большой смысл, когда большинство сетей были общей шиной. Все пакеты отправлялись в любом случае, это только говорило каждому получателю, чтобы он принимал их. Посылка на широковещательный адрес была более эффективной, чем посылка двух копий пакета, если два человека хотели получить его.
Для коммутируемых сетей это не тот случай. Каждый компьютер может отправлять и получать определенное количество данных, и коммутатор будет распределять эти пакеты между машинами. Так, 4 компьютера в сети 100 Mb/s могут иметь 2 независимых передачи на 100Mb/s. Если вы отправляете пакет на широковещательный адрес, он рассылается всем, даже тем, кто не хочет его получать.
Групповая передача немного умнее. Она определяет группы компьютеров и назначает им общий IP адрес. Пакеты, отсылаемые на этот адрес, направляются на каждый компьютер, который решил войти в группу. В отличие от широковещательной передачи, групповая передача маршрутизируема. Вы можете иметь множество компьютеров в различных сетях в multicast-группе, и генерировать только два пакета, если исходный пакет достигает маршрутизатора, за которым имеются участники, состоящие в двух нижестоящих сетях. Если, к примеру, ваша интернет-радиостанция использует групповую передачу IPv6, то радиостанция будет передавать один поток пакетов вашему провайдеру. Ваш провайдер должен будет отправить копию потока для каждого из своих клиентов, которые слушают эту станцию. Когда пакеты попадут в ваш маршрутизатор, он отправит копию каждой машине в вашей сети, которая слушает этот поток.
Вы можете использовать тот же механизм для таких вещей, как конференц-звонки. В настоящее время, если у вас есть 10 человек в сессии видеоконференции, то либо каждый должен выдавать 10 копий потока со своей камеры, либо вам необходимо иметь где-то сервер, который сможет справиться с ретрансляцией. При групповой рассылке, все отправляли бы один экземпляр для 10 человек. В потребительских сетях, которые обычно имеют канал на прием гораздо шире, чем на отдачу, это особенно привлекательно.
Широковещательная трансляция также используется, когда вы не знаете, например, какой компьютер вы должны использовать для обнаружения сервисов. IPv6 заменяет этот способ при помощи anycast. Anycast — это нечто, похожее на multicast в группе адресов, ассоциированных с anycast-адресом, но в отличие от multicast, пакеты, отправленные на этот адрес доставляются только на одну машину. Это полезно для таких вещей, как, например, автоконфигурация.
Сокеты и протоколы
Надеюсь, что теперь вы можете видеть, что поддержка IPv6 является полезной. Тогда возникает вопрос — как? Большинство сетевого кода либо использует сокеты Беркли или высокоуровневый API, построенный на них. Если вы используете высокоуровневый API, то вы, вероятно, уже имеете поддержку IPv6, предоставляемую кем-то другим. Если вы используете сокеты, то вам необходимо внести небольшие изменения в код.
API сокетов Беркли был основан на UNIX идее, что все есть файл. Вы общаетесь с удаленными машинами, используя дескрипторы с некоторыми дополнительными особенностями. Если у вас есть сокет, то API полностью протоколо-независимый. Используете ли вы IPv4, IPv6, AppleTalk, или какой-либо другой протокол, код будет одинаковый.
К сожалению, создание сокетов — это то место, где начинаются проблемы. Вы создаете сокет при помощи такой функции:
int socket(int domain, int type, int protocol);
Три параметра полностью определяют некоторые аспекты протокола. В большинстве случаев в конечном итоге вы жестко задаете domain как PF_INET, подразумевая IPv4. Все становится еще хуже, когда вы связываете сокет с локальным адресом, а затем или подключаетесь, или принимаете подключения. Все соответствующие функции принимают указатель на структуру sockaddr в качестве аргумента.
Эта структура довольно проста, но вы никогда не используете ее напрямую. Вместо этого вы используете что-то вроде структуры sockaddr_in, которая начинается так же, как структура sockaddr (с размером и семейством адреса), но содержит IPv4-адрес и номер порта. Вы обычно используете gethostbyname(), чтобы определить эти параметры.
Если вы хотите поддерживать еще один протокол, то вам необходимо добавить другую ветвь кода с другим механизмом определения. Одним из последних усовершенствований в версии POSIX от 2004 года является определение функции getaddrinfo(). Она определяет сервер, заданный строковым описанием адреса и сервиса. Это означает, что вам не придется жестко задавать номера портов. Использовать ее довольно просто. Этот код будет подключаться к серверу InformIT по HTTP, используя любые доступные соединения:
struct addrinfo hints, *results;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int error = getaddrinfo("informit.com", "http", &hints, &results);
if (error) { /* fail */ return -1; }
int s = -1;
for (struct addrinfo *res = results;
res != NULL && s < 0 ;
res = res->ai_next)
{
s = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
//If the socket failed, try the next address
if (s < 0) { continue ; }
//If the connection failed, try the next address
if (connect(s, res->ai_addr, res->ai_addrlen) < 0)
{
close(s);
s = -1;
continue;
}
}
freeaddrinfo(results);
return s;
* This source code was highlighted with Source Code Highlighter.
Первые два аргумента getaddrinfo() являются именем сервера и именем протокола. Преобразователь найдет сочетание адреса сервера и сервиса. На некоторых платформах, таких как OS X, это даже включает в себя определение записи DNS SRV, так что эта операция настроит порт правильно, даже если сервер работает на нестандартном порту, пока запись DNS содержит этот факт. Структура hints используется для задания некоторых ограничений на возвращаемую информацию об адресе. В этом случае, мы хотим только поточные сокеты, и мы готовы принять их в абсолютно любом протоколе, поддерживаемом сетевым стеком. Последний аргумент является указателем, который используется для возврата массива структур информации об адресах.
Обратите внимание, что все аргументы в socket() и connect() целиком являются данными об адресах, возвращенными вызовом функции getaddrinfo(). Ничто из этого кода вообще не говорит о том, используете ли вы IPv4, IPv6, или какой-либо другой новый протокол. До тех пор, пока ваша операционная система поддерживает протокол, этот код можно использовать.
IPv6: Протокол современности
Надеюсь, что в этой статье вы увидели, что IPv6 предоставляет некоторые захватывающие особенности, которые делают его заслуживающим поддержки. И, самое главное, что поддержка его стоит не так уж много усилий. Нет никаких оправданий для написания нового кода сокетов, который не поддерживает IPv6. И так же довольно просто обновление старого кода для его поддержки.
Я должен был рассмотреть функцию getaddrinfo(), когда я писал эту статью, потому что я обернул ее в класс, который берет имена хоста и протокола в качестве аргументов и через некоторое время возвращает соединенный сокет, и сейчас я просто использую этот класс во всем сетевом коде.
Свободных IPv4 адресов становится все меньше и меньше, и похоже, что все больше провайдеров используют NAT для всех своих клиентов. В какой-то момент устанавливать соединения end-to-end друг с другом напрямую смогут только те, кто использует приложения, которые поддерживают протокол IPv6 «из коробки». Если ваш код до сих пор не может этого, сейчас самое время исправить его.