Я привык писать код с использованием IPv4, где вы можете использовать recvfrom и sendto для ответа отправителю, но с IPv6 все кажется немного сложнее. Хотя я сейчас работаю в Linux, этот вопрос больше касается IPv6 и локальных адресов в целом.
Компьютер A: fe80::eabf:bbff:cae0:8d3e Компьютер Б: fe80::da3a:ddff:fee3:e257
Чтобы пропинговать компьютер A с компьютера B, я могу:
ping fe80::eabf:bbff:cae0:8d3e%eth0
или отправлять пакеты на компьютер А, я могу:
echo "xyz" | nc fe80::eabf:bbff:cae0:8d3e%eth0 5353 -u -w 1
На компьютере А я могу выполнить:
struct sockaddr_in6 sin6 = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_ANY_INIT,
.sin6_port = htons( MDNS_PORT )
};
sd6 = socket( AF_INET6, SOCK_DGRAM, 0 );
if ( bind( sd6, (struct sockaddr *)&sin6, sizeof(sin6) ) == -1 ); // Checks (didn't fail)
...
struct sockaddr sender = { 0 };
socklen_t sl = sizeof( sender );
int r = recvfrom( sock, buffer, sizeof(buffer), 0, (struct sockaddr*) &sender, &sl );
printf( "%d / %d\n", r, sender.sin6_family );
for( i = 0; i < sl; i++ )
printf( "%02x ", ((uint8_t*)&sender)[i] );
printf( "\n" );
sendto( sock, "hello", 5, 0, &sender, sl );
И при отправке строки nc сверху я получаю следующее:
4 / 10
0a 00 a9 89 00 00 00 00 fe 80 00 00 00 00 00 00 da 3a dd ff fe e3 e2 57 02 00 00 00
В этом есть смысл: это адрес компьютера B. Но во многих случаях пакет так и не передается. Я думаю, это потому, что иногда, если я делаю холодный пинг компьютера B с компьютера A, пинги тоже никогда не проходят.
Без сопоставления, определяющего интерфейс, кажется, что IPv6 не всегда может быть разрешен.
Как мне сделать так, чтобы мои ответы на пакеты IPv6 возвращались через интерфейс, откуда они пришли?
Прежде чем зайти слишком далеко по пути recvmsg, похоже, это не работает для IPv6 так же, как для IPv4, что касается IPv6, при использовании recvmsg нет управляющей полезной нагрузки и, следовательно, нет возможности по-настоящему ответить sendto -- РЕДАКТИРОВАТЬ : Есть способ - использовать setsockopt с IPPROTO_IPV6, IPV6_RECVPKTINFO
Есть идеи? Предложения?
Я только что отредактировал вопрос, чтобы добавить эту информацию, @user207421.
Кажется, это работает гораздо надежнее, если я использую sockaddr_in6 для Recvfrom вместо sockaddr. Я сообщу, если это не удастся вовремя. Если это кажется надежным, я думаю, это и есть ответ на вопрос. Похоже, sin6_scope_id имеет значение! Я заметил, что оно правильно заполняется, когда приходит сообщение с удаленного адреса!
Ну, вам обязательно стоит использовать sockaddr_in6. Вы не можете получить литр из пинты горшка.
Не используйте адресацию Link-Local, которая относится к одной и той же сети для каждого интерфейса. Используйте что-то вроде ULA или глобальной адресации, которая будет однозначно идентифицировать источник. «Адреса Link-Local предназначены для использования для адресации по одному каналу в таких целях, как автоматическая настройка адреса, обнаружение соседей или когда маршрутизаторы отсутствуют». Если вы хотите использовать обычную сеть, используйте что-то отличное от локальной адресации.
@RonMaupin: Судя по использованию MDNS_PORT в сообщении ОП, они на самом деле пишут код «для таких целей, как автоматическая настройка адреса или обнаружение соседей» - под что подпадает mDNS со стандартным использованием локального IPv6.
mDNS должен использовать многоадресную рассылку, отсюда и буква «m» в mDNS.
Да, но если установлен флаг UNICAST-RESPONSE, вы должны возвращать ответы непосредственно отправителю.





Если вы объявите переменную как struct sockaddr, то она будет меньше хранящейся в ней sockaddr_in6 — в моей системе она на целых 12 байт меньше — что приводит к переполнению буфера каждый раз, когда recvfrom() пытается поместить туда адрес inet6. Либо recvfrom() перезаписывает другую переменную за пределами структуры (возможно, он помещает мусор в переменную 'sl' при сохранении адреса), либо наоборот (если recvfrom() обновляет &sl новым размером, он может перезаписать часть адреса) .
Общий тип, подходящий всем, который вам нужен, будет struct sockaddr_storage.
struct sockaddr мал, потому что он предшествует IPv6, а увеличение размера общедоступного типа в Linux не работает, поэтому он полезен только при использовании для приведения указателей. (Кстати, многие проекты в конечном итоге изобретают union sockaddr_any или что-то подобное, как более удобный способ избежать приведения типов.)
(Да, sin6_scope_id имеет значение, но recvfrom() всегда заполняет его — в любом случае он думает, что заполняет sockaddr_in6.)
Я почти уверен, что это правильно. Если у меня больше не возникнет проблем, я скоро отмечу этот ответ как правильный.
Да, я могу подтвердить, что именно это и происходило. Спасибо за пояснение!
Как ты привязал розетку?