I would personally bind the UDP server to the wildcard address and specific port, and use the IP_PKTINFO
socket option to obtain the interface and destination address as an ancillary message to each packet.
Essentially, enabling IP_PKTINFO
socket options means you receive a IPPROTO_IP
level IP_PKTINFO
type ancillary message
with each packet you receive using recvmsg()
.
Similarly, when sending a response, you can use the ipi_ifindex
or ipi_spec_dst
members in an ancillary IP_PKTINFO
message to tell the kernel how to route the message.
This way, you can bind to just one (or two, if you use both IPv4 and IPv6) wildcard socket, and use it to both receive and send UDP packets via any interface you want; in particular, using the same interface and source IP address the client used as the destination. Whenever new interfaces become available, your server side immediately responds to those as well (although it can obviously just drop unwanted client requests on the floor, based on the interface they came from). Simple, and quite robust.
Perhaps the following example server.c illustrates this better:
#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;
}
Compile using e.g. gcc -Wall -O2 server.c -o server
, and run specifying the port number as a command line parameter. For example, ./server 4044
.
For testing, on the client side I used netcat: echo 'Hello!' | nc -q 1 -u theipaddress 4044
.
Because it is late Friday night when I am writing this, and I'm too lazy to set up some additional devices, I've only tested this very lightly, and only on one machine. The logic is sound; it's only my implementation that might be off.
If you have any questions or see a bug or blatant error, do let me know in a comment so I can verify and fix.