Я бы лично связал сервер UDP с подстановочным адресом и конкретным портом и использовал опцию сокета IP_PKTINFO
для получения интерфейса и адреса назначения в качестве вспомогательного сообщения для каждого пакета.
По сути, включение опций сокета IP_PKTINFO
означает, что вы получаете IPPROTO_IP
уровень IP_PKTINFO
тип ancillary message
с каждым полученным вами пакетом, используя recvmsg()
.
Аналогично, при отправке ответа вы можете использовать элементы ipi_ifindex
или ipi_spec_dst
во вспомогательном сообщении IP_PKTINFO
, чтобы сообщить ядру, как маршрутизировать сообщение.
Таким образом, выможет связываться только с одним (или двумя, если вы используете как IPv4, так и IPv6) подстановочным сокетом и использовать его для приема и отправки пакетов UDP через любой интерфейс, который вы хотите;в частности, используя тот же интерфейс и IP-адрес источника, который клиент использовал в качестве пункта назначения.Всякий раз, когда становятся доступными новые интерфейсы, ваша серверная сторона также немедленно реагирует на них (хотя, очевидно, она может просто отбрасывать нежелательные клиентские запросы на уровне, основываясь на интерфейсе, с которого они пришли).Простой и достаточно надежный.
Возможно, следующий пример server.c лучше иллюстрирует это:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <net/if.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
if (!done)
done = signum;
}
static int install_done(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done;
act.sa_flags = 0;
return sigaction(signum, &act, NULL);
}
static inline const char *ip4_address(const struct in_addr addr)
{
static char buffer[32];
char *p = buffer + sizeof buffer;
unsigned char octet[4];
/* in_addr is in network byte order. */
memcpy(octet, &addr, 4);
/* We build the string in reverse order. */
*(--p) = '\0';
do {
*(--p) = '0' + (octet[3] % 10);
octet[3] /= 10;
} while (octet[3]);
*(--p) = '.';
do {
*(--p) = '0' + (octet[2] % 10);
octet[2] /= 10;
} while (octet[2]);
*(--p) = '.';
do {
*(--p) = '0' + (octet[1] % 10);
octet[1] /= 10;
} while (octet[1]);
*(--p) = '.';
do {
*(--p) = '0' + (octet[0] % 10);
octet[0] /= 10;
} while (octet[0]);
return p;
}
int main(int argc, char *argv[])
{
int ip4fd, ip4port;
char dummy;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s UDP-PORT-NUMBER\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
if (sscanf(argv[1], " %d %c", &ip4port, &dummy) != 1 || ip4port < 1 || ip4port > 65535) {
fprintf(stderr, "%s: Invalid UDP port number.\n", argv[1]);
return EXIT_FAILURE;
}
if (install_done(SIGHUP) ||
install_done(SIGINT) ||
install_done(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
ip4fd = socket(AF_INET, SOCK_DGRAM, 0);
if (ip4fd == -1) {
fprintf(stderr, "Cannot create an UDP socket: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Set the IP_PKTINFO socket option, so each received datagram has an
ancillary message containing a struct in_pktinfo. */
{
int option = 1;
if (setsockopt(ip4fd, IPPROTO_IP, IP_PKTINFO, &option, sizeof option) == -1) {
fprintf(stderr, "Cannot set IP_PKTINFO socket option: %s.\n", strerror(errno));
close(ip4fd);
return EXIT_FAILURE;
}
}
/* Bind to the wildcard address, to receive packets using any network interface. */
{
struct sockaddr_in ip4addr;
ip4addr.sin_family = AF_INET;
ip4addr.sin_port = htons(ip4port);
ip4addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(ip4fd, (const struct sockaddr *)(&ip4addr), sizeof ip4addr) == -1) {
fprintf(stderr, "Cannot bind to port %d: %s.\n", ip4port, strerror(errno));
close(ip4fd);
return EXIT_FAILURE;
}
}
printf("Now listening on UDP port %d.\n", ip4port);
printf("Press CTRL+C, or send HUP, INT, or TERM (pid %ld) to exit.\n",
(long)getpid());
fflush(stdout);
/* Receive UDP messages, and describe them. */
{
unsigned char payload[4096], ancillary[1024];
char *iface, ifacebuf[IF_NAMESIZE + 1];
unsigned int iface_index;
struct in_addr iface_addr, dest_addr;
struct iovec iov;
struct msghdr hdr;
struct cmsghdr *cmsg;
struct sockaddr_in from;
struct in_pktinfo *info;
ssize_t len;
size_t i;
while (!done) {
iov.iov_base = payload;
iov.iov_len = sizeof payload;
hdr.msg_name = &from;
hdr.msg_namelen = sizeof from;
hdr.msg_iov = &iov;
hdr.msg_iovlen = 1;
hdr.msg_control = ancillary;
hdr.msg_controllen = sizeof ancillary;
hdr.msg_flags = 0;
/* Receive a new datagram. */
len = recvmsg(ip4fd, &hdr, 0);
if (len < 0) {
if (len == -1) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
continue;
fprintf(stderr, "Error receiving data: %s.\n", strerror(errno));
} else
fprintf(stderr, "recvmsg() error: Unexpected return value, %zd.\n", len);
close(ip4fd);
return EXIT_FAILURE;
}
/* Report. */
printf("Received %zu bytes from %s port %d:\n",
(size_t)len, ip4_address(from.sin_addr), ntohs(from.sin_port));
/* Check the ancillary data for the pktinfo structure. */
info = NULL;
for (cmsg = CMSG_FIRSTHDR(&hdr); cmsg != NULL; cmsg = CMSG_NXTHDR(&hdr, cmsg))
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
info = (void *)CMSG_DATA(cmsg);
if (!info) {
fprintf(stderr, "Error: Packet is missing the IP_PKTINFO ancillary information!\n");
close(ip4fd);
exit(EXIT_FAILURE);
}
/* info may be unaligned. */
memcpy(&iface_index, &(info->ipi_ifindex), sizeof info->ipi_ifindex);
memcpy(&iface_addr, &(info->ipi_spec_dst), sizeof info->ipi_spec_dst);
memcpy(&dest_addr, &(info->ipi_addr), sizeof info->ipi_addr);
iface = if_indextoname(info->ipi_ifindex, ifacebuf);
/* Report the IP_PKTINFO information. */
if (iface)
printf(" Interface: %u (%s)\n", iface_index, iface);
else
printf(" Interface: %u\n", iface_index);
printf(" Local address: %s port %d\n", ip4_address(iface_addr), ip4port);
printf(" Real destination: %s port %d\n", ip4_address(dest_addr), ip4port);
for (i = 0; i < (size_t)len; i++) {
if (i == 0)
printf(" Data: 0x%02x", payload[i]);
else
if ((i & 15) == 0)
printf("\n 0x%02x", payload[i]);
else
printf(" 0x%02x", payload[i]);
}
if (len > 0)
printf("\n");
fflush(stdout);
/*
* Construct a response.
*/
payload[0] = 'O';
payload[1] = 'k';
payload[2] = '!';
payload[3] = '\n';
iov.iov_base = payload;
iov.iov_len = 4;
/* Keep hdr.msg_name and hdr.msg_namelen intact. */
hdr.msg_iov = &iov;
hdr.msg_iovlen = 1;
/* Prep the ancillary data. */
hdr.msg_control = ancillary;
hdr.msg_controllen = CMSG_SPACE(sizeof (struct in_pktinfo));
cmsg = CMSG_FIRSTHDR(&hdr);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof (struct in_pktinfo));
info = (void *)CMSG_DATA(cmsg);
/* info may be unaligned. */
memcpy(&(info->ipi_ifindex), &iface_index, sizeof info->ipi_ifindex);
memcpy(&(info->ipi_spec_dst), &iface_addr, sizeof info->ipi_spec_dst);
memcpy(&(info->ipi_addr), &from.sin_addr, sizeof info->ipi_addr);
hdr.msg_flags = 0;
/* Send the response. */
do {
len = sendmsg(ip4fd, &hdr, MSG_NOSIGNAL);
} while (len == -1 && errno == EINTR);
if (len == -1) {
fprintf(stderr, "Cannot send a response message: %s.\n", strerror(errno));
close(ip4fd);
return EXIT_FAILURE;
}
printf(" %zd-byte response sent successfully.\n", len);
fflush(stdout);
}
}
close(ip4fd);
return EXIT_SUCCESS;
}
Компиляция с использованием, например, gcc -Wall -O2 server.c -o server
и запуск с указанием портачисло в качестве параметра командной строки.Например, ./server 4044
.
Для тестирования на стороне клиента я использовал netcat: echo 'Hello!' | nc -q 1 -u theipaddress 4044
.
Поскольку я пишу это поздно вечером в пятницу, и яслишком ленив, чтобы настроить дополнительные устройства, я проверил это очень легко и только на одной машине.Логика звучит;только моя реализация может быть отключена.
Если у вас есть какие-либо вопросы или вы видите ошибку или явную ошибку, сообщите мне об этом в комментарии, чтобы я мог проверить и исправить.