Подключение клиента IPv4 к серверу IPv6: в соединении отказано

Я экспериментирую с сокетами IPv6, особенно с возможностью «двойного стека», предлагаемой в Windows Vista и более поздних версиях, и, очевидно, по умолчанию в Unix. Я обнаружил, что когда я привязываю свой сервер к определенному IP-адресу или к разрешению имени хоста моей локальной машины, я не могу принять соединение от клиента IPv4. Однако, когда я привязываюсь к INADDR_ANY, я могу.

Пожалуйста, рассмотрите следующий код для моего сервера. Вы можете видеть, что я следую совету Microsoft по созданию сокета IPv6, а затем устанавливаю флаг IPV6_V6ONLY на ноль:

addrinfo* result, *pCurrent, hints;

memset(&hints, 0, sizeof hints);    // Must do this!
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // We intend to use the addrinfo in a call to connect().  (I know it is ignored if we specify a server to connect to...)

int nRet = getaddrinfo("powerhouse", "82", &hints, &result);

SOCKET sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

int no = 0;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&no, sizeof(no)) != 0)
    return -1;

if (bind(sock, result->ai_addr, result->ai_addrlen) ==  SOCKET_ERROR)
    return -1;

if (listen(sock, SOMAXCONN) == SOCKET_ERROR)
    return -1;

SOCKET sockClient = accept(sock, NULL, NULL);

Вот код моего клиента. Вы можете видеть, что я создаю сокет IPv4 и пытаюсь подключиться к моему серверу:

addrinfo* result, *pCurrent, hints;

memset(&hints, 0, sizeof hints);    // Must do this!
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;

if (getaddrinfo("powerhouse", "82", &hints, &result) != 0)
    return -1;

SOCKET sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
int nRet = connect(sock, result->ai_addr, result->ai_addrlen);

Результатом моего вызова соединения всегда будет 10061: соединение отклонено.

Если я изменяю код сервера для привязки к :: (или передаю NULL-хост в getaddrinfo () (то же самое)) и изменяю свой клиентский код, чтобы указать NULL-хост в вызове getaddrinfo (), то клиент V4 может подключиться отлично.

Кто-нибудь может объяснить, пожалуйста, почему? Я не читал ничего, что мы должны указать хост NULL (следовательно, использовать INADDR_ANY), если мы хотим работать с двумя сокетами. Это не может быть требованием, потому что у меня многосетевой хост, и я хочу принимать IPv4 только на некоторых из доступных IP-адресов?

ИЗМЕНИТЬ 15.05.2013:

Это соответствующая документация, которая сбила меня с толку относительно того, почему мой код не работает:

Из сокетов двойного стека для приложений Winsock IPv6

«Windows Vista и более поздние версии предлагают возможность создания одного сокета IPv6, который может обрабатывать трафик как IPv6, так и IPv4. Например, сокет прослушивания TCP для IPv6 создается, переводится в режим двойного стека и привязан к порту 5001. Этот двойной- сокет стека может принимать соединения от клиентов TCP IPv6, подключающихся к порту 5001, и от клиентов TCP IPv4, подключающихся к порту 5001. "

"По умолчанию сокет IPv6, созданный в Windows Vista и более поздних версиях, работает только по протоколу IPv6. Чтобы преобразовать сокет IPv6 в сокет с двойным стеком, необходимо вызвать функцию setsockopt с параметром сокета IPV6_V6ONLY, чтобы установить это значение на ноль до того, как сокет будет привязан к IP-адресу. Когда для параметра сокета IPV6_V6ONLY установлено значение 0, сокет, созданный для семейства адресов AF_INET6, может использоваться для отправки и получения пакетов на и от IPv6-адреса или сопоставленного IPv4 адрес. (выделено мной) "


person Wad    schedule 10.05.2013    source источник
comment
Для подключений IPv4 вам нужна конечная точка IPv4, bind явно является конечной точкой IPv6, а не подстановочным знаком.   -  person Steve-o    schedule 10.05.2013
comment
@ Steve-o, прости, я не понимаю, что ты имеешь в виду. Вы можете уточнить?   -  person Wad    schedule 10.05.2013
comment
Привязка реализует фильтр, чтобы принимать только данные с адресом назначения, совпадающим с привязанным адресом. Пакеты IPv4 будут иметь адрес назначения IPv4, поэтому будут отклонены.   -  person Steve-o    schedule 10.05.2013
comment
@ Steve-o Спасибо, теперь я понимаю принцип, но, вызвав setsockopt с IPPROTO_IPV6, я - согласно документации - разрешил этому сокету также принимать входящие соединения IPv4. Пожалуйста, также смотрите комментарий, который я разместил ниже, к комментарию Сандера ...   -  person Wad    schedule 13.05.2013


Ответы (2)


IPv4 и IPv6 - два отдельных протокола. Пакеты одного протокола не могут обрабатываться с использованием другого протокола. Вот почему существует концепция двойного стека: ваша система работает со стеками протоколов IPv4 и IPv6, имеет адреса IPv4 и IPv6 и т. Д.

В операционных системах есть уловка, при которой у вас может быть сокет IPv6, который прослушивает все адреса IPv4 и IPv6. Вам по-прежнему необходимо иметь оба семейства адресов на хосте, и это работает только при привязке к адресу с подстановочными знаками. Как только вы привяжете этот сокет к фиксированному адресу, он больше не работает и будет работать только для адреса, к которому вы привязаны.

Итак, если вы хотите прослушивать все доступные адреса, тогда установите IPV6_V6ONLY на 0 и прослушайте групповой адрес. Клиенты IPv4 будут показаны как использующие адреса IPv6, начинающиеся с ::ffff:, причем последние 32 бита содержат адрес IPv4.

Если вы хотите привязаться к определенным адресам, вам понадобятся сокеты, привязанные к каждому из адресов, которые вы хотите прослушивать. Затем вам нужно использовать, например, select(...), чтобы контролировать эти сокеты и реагировать на те, которые становятся активными из-за того, что кто-то подключается к ним.

person Sander Steffann    schedule 10.05.2013
comment
Спасибо за комментарий Сандера, ваш комментарий имеет смысл. Не могли бы вы дать ссылку на то, где это задокументировано - мне не удалось ее найти. Например, msdn.microsoft .com / en-us / library / windows / desktop /, похоже, заявляет, что единственное, что нам нужно сделать, это установить IPV6_V6ONLY на ноль ... - person Wad; 10.05.2013
comment
Официальная документация - RFC 3493: tools.ietf.org/html/rfc3493.html . Но этот сценарий там не описан, потому что это технически невозможно. Если вы привязываете сервер к IPv6-адресу, клиент сможет подключиться только при использовании этого точного адреса в качестве адреса назначения. Клиент, работающий только с IPv4, никогда не может подключиться к адресу IPv6, точно так же, как клиент, работающий только с IPv6, никогда не сможет подключиться к адресу IPv4. Источник и место назначения всегда должны быть одного и того же протокола. IPv6 не имеет обратной совместимости с IPv4 на проводе ... - person Sander Steffann; 10.05.2013
comment
Спасибо, Сандер. Я привязываюсь только к адресам IPv6, да, но, как было сказано выше Стиву, установив IPPROTO_IPV6, в документации утверждается, что мой сокет IPv6 также может принимать клиента IPv4: адрес IPv4 будет сопоставлен с IPv6. Действительно, когда я запускаю свой сервер и смотрю на TCPView, после вызова setsockopt () я вижу два связанных сокета на powerhouse, по одному для каждого протокола IPv4 и IPv6. Вот почему я не могу понять, почему это все еще не работает ... - person Wad; 13.05.2013
comment
Это действительно работает, если вы привязываетесь к адресу для приема всей почты домена. Вы привязываете сервер к конкретному реальному IPv6-адресу. Но клиент не может, потому что клиенту, работающему только с IPv4, требуется адрес назначения IPv4 для подключения, а его не существует, потому что вы привязываете сервер только к IPv6-адресу. Если вы хотите, чтобы сервер принимал соединения IPv4 через сокет IPv6, вам необходимо привязать сервер к IPv4-адресу, отображаемому в IPv6, в дополнение к глобальному адресу IPv6, к которому вы привязываетесь. Вам необходимо предоставить IPv4-адрес для подключения клиента. - person Sander Steffann; 13.05.2013
comment
Аааааааааааааааааааа! Извините, Сандер ... так почему TCPView показывает мне, что у меня есть прослушивающий сокет TCP IPv4, а также прослушивающий сокет TCP IPv6 (просто установив IPPROTO_IPV6 на 0), если я не могу подключиться к сокету IPv4 TCP? Установка IPPROTO_IPV6 на 0 полезна только при привязке к адресу для приема всей почты домена, иначе это бесполезно ?? - person Wad; 14.05.2013
comment
Не нужно извиняться! Nitpicking: Вы говорите о IPV6_V6ONLY :) Вы указываете IPv6-адрес для прослушивания. На каком IPv4-адресе тогда слушает ваше приложение? Установка IPV6_V6ONLY в 0 может быть полезна , если вы также привязываете сервер к IPv4-адресу, отображаемому в IPv6. - person Sander Steffann; 15.05.2013
comment
Сандер, я отредактировал свой вопрос, включив в него текст MSDN, который заставил меня задать этот вопрос. Кажется, все, что мы здесь обсуждали, противоречит этому. Если, конечно, я не правильно читаю. Не могли бы вы взглянуть на мою правку / ссылку и уточнить, пожалуйста; то, что казалось таким простым в этой статье, оказалось настоящей проблемой! - person Wad; 15.05.2013
comment
Эта часть: используемая для отправки и получения пакетов на IPv6-адрес или сопоставленный IPv4-адрес и обратно, сбивает с толку. Глядя на программный уровень, этот сокет может работать с адресами IPv6 и IPv6-mapped-IPv4. Он больше ничего не говорит, а по сети IPv4 и IPv6 несовместимы. Таким образом, хосту IPv4 требуется адрес назначения IPv4 для подключения. Адреса IPv6-mapped-IPv4 на самом деле являются IPv4-адресами, представленными как IPv6-адреса. Привязка сокета IPv6 к такому адресу фактически привязывается к адресу IPv4. Привязка только к IPv6-адресу сделает невозможным подключение IPv4-клиентов. - person Sander Steffann; 17.05.2013
comment
Сандер благодарит за комментарий; Я был вдали от машины IPv6, поэтому я не могу выполнить свой код и применить то, что вы сказали, я свяжусь с вами как можно скорее. - person Wad; 23.05.2013
comment
Сандер, я уже достаточно спросил по этому вопросу и перешел к новым связанным запросам на stackoverflow.com/questions/16762939/. Большое спасибо за вашу помощь по этому вопросу. Пожалуйста, обратите внимание на мой новый вопрос. - person Wad; 26.05.2013
comment
Я нахожу этот ответ очень запутанным (здесь 30-летний профессиональный сетевой программист) с первоначальным утверждением: IPv4 и IPv6 - два отдельных протокола. Пакеты одного протокола не могут обрабатываться с использованием другого протокола. На уровне IP это верно, и все поставляемые операционные системы сегодня поддерживают IPv4 и IPv6, но уровни TCP или UDP могут легко работать на обоих. Открытие сокета на уровне TCP может быть выполнено независимо, НЕ устанавливая IPV6_V6ONLY. Просто знай, чего хочешь. - person Brian Bulkowski; 18.12.2015

Эта ссылка http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch12lev1sec2.html содержит дополнительную информацию о соединении IPv4 и IPv6,

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

  • Прослушивающий сокет IPv4 может принимать входящие соединения только от клиентов IPv4.
  • Если на сервере есть прослушивающий сокет IPv6, связанный с подстановочным адресом, а параметр сокета IPV6_V6ONLY (раздел 7.8) не установлен, этот сокет может принимать входящие соединения от клиентов IPv4 или клиентов IPv6. Для подключения от клиента IPv4 локальным адресом сервера для подключения будет соответствующий IPv4-адрес с отображением IPv4.
  • Если у сервера есть прослушивающий сокет IPv6, который привязал IPv6-адрес, отличный от IPv4-сопоставленного адреса IPv6, или привязал подстановочный адрес, но установил параметр сокета IPv6_V6ONLY (раздел 7.8), этот сокет может принимать входящие соединения от клиентов IPv6. Только.
person Sathish    schedule 08.03.2015