Я думал, что расскажу Джереми, как это сделать для IPv6. Джереми пропускает много деталей, и некоторая документация (например, справочная страница Linux для ipv6) просто неверна. Сначала в некоторых дистрибутивах вы должны определить _GNU_SOURCE, иначе некоторые вещи IPv6 не определены:
#define _GNU_SOURCE
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
Затем настройте сокет довольно стандартным способом, который прослушивает все IP-пакеты (т. Е. Как IPv4, так и IPv6) на конкретном порте UDP:
const int on=1, off=0;
int result;
struct sockaddr_in6 sin6;
int soc;
soc = socket(AF_INET6, SOCK_DGRAM, 0);
setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
setsockopt(soc, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
setsockopt(soc, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on));
setsockopt(soc, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off));
memset(&sin6, '\0', sizeof(sin6));
sin6.sin6_family = htons(AF_INET6);
sin6.sin6_port = htons(MY_UDP_PORT);
result = bind(soc, (struct sockaddr*)&sin6, sizeof(sin6));
Обратите внимание, что приведенный выше код устанавливает параметры IP и IPv6 для сокета IPv6. Оказывается, если пакет приходит на IPv4-адрес, вы получите cmsg IP_PKTINFO (т.е. IPv4), даже если это сокет IPv6, и если вы не включите их, они не будут отправлены. Также обратите внимание, что установлена опция IPV6_RECPKTINFO (которая не упоминается в man 7 ipv6 ), а не IPV6_PKTINFO (что неправильно описано в man 7 ipv6 ). Теперь получите пакет udp:
int bytes_received;
struct sockaddr_in6 from;
struct iovec iovec[1];
struct msghdr msg;
char msg_control[1024];
char udp_packet[1500];
iovec[0].iov_base = udp_packet;
iovec[0].iov_len = sizeof(udp_packet);
msg.msg_name = &from;
msg.msg_namelen = sizeof(from);
msg.msg_iov = iovec;
msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec);
msg.msg_control = msg_control;
msg.msg_controllen = sizeof(msg_control);
msg.msg_flags = 0;
bytes_received = recvmsg(soc, &msg, 0);
Следующим шагом является извлечение интерфейса и адреса, по которому пакет UDP был получен из cmsg:
struct in_pktinfo in_pktinfo;
struct in6_pktinfo in6_pktinfo;
int have_in_pktinfo = 0;
int have_in6_pktinfo = 0;
struct cmsghdr* cmsg;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0; cmsg = CMSG_NXTHDR(&msg, cmsg))
{
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
{
in_pktinfo = *(struct in_pktinfo*)CMSG_DATA(cmsg);
have_in_pktinfo = 1;
}
if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO)
{
in6_pktinfo = *(struct in6_pktinfo*)CMSG_DATA(cmsg);
have_in6_pktinfo = 1;
}
}
Наконец, мы можем отправить ответ обратно, используя тот же пункт назначения.
int cmsg_space;
iovec[0].iov_base = udp_response;
iovec[0].iov_len = udp_response_length;
msg.msg_name = &from;
msg.msg_namelen = sizeof(from);
msg.msg_iov = iovec;
msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec);
msg.msg_control = msg_control;
msg.msg_controllen = sizeof(msg_control);
msg.msg_flags = 0;
cmsg_space = 0;
cmsg = CMSG_FIRSTHDR(&msg);
if (have_in6_pktinfo)
{
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
*(struct in6_pktinfo*)CMSG_DATA(cmsg) = in6_pktinfo;
cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo));
}
if (have_in_pktinfo)
{
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
*(struct in_pktinfo*)CMSG_DATA(cmsg) = in_pktinfo;
cmsg_space += CMSG_SPACE(sizeof(in_pktinfo));
}
msg.msg_controllen = cmsg_space;
ret = sendmsg(soc, &msg, 0);
Снова обратите внимание, как, если пакет поступил через IPv4, мы должны поместить опцию IPv4 в cmsg, даже если это сокет AF_INET6. По крайней мере, это то, что вы должны сделать для Linux.
Это удивительный объем работы, но AFAICT - это минимум, который вам нужно сделать, чтобы создать надежный UDP-сервер, работающий во всех мыслимых средах Linux. Большая часть этого не требуется для TCP, потому что он прозрачно обрабатывает множественную адресацию.