1

I'm trying to write an application in which a client should send UDP packets to the server, which should then reply to the client, all through wireless interfaces. Both client and server are implemented inside the same binary file, and a mode can be selected by the user using proper command line parameters.

I'm using UDP, but I'm having problems making the client and sever communicate. First of all, I'm trying to use the same UDP socket to receive and send packets, in both cases. I was thinking that it was possible, but I'm starting to have some doubts.

Then, this is the relevant code for the client and server:

        struct sockaddr_in inaddr;

        fd=socket(AF_INET,SOCK_DGRAM,0);

        if(fd==-1) {
            perror("socket() error");
            exit(EXIT_FAILURE);
        }

        // Prepare sockaddr_in structure
        bzero(&inaddr,sizeof(inaddr));
        inaddr.sin_family=AF_INET;
        inaddr.sin_port=htons(opts.port); // opts.port is parsed from the command line
        inaddr.sin_addr.s_addr=opts.destIPaddr.s_addr; // opts.destIPaddr is parsed from the command line and already in the correct format

        // Bind to the wireless interface (devname is previusly obtained in a tested piece of code)
        if(setsockopt(sData.descriptor,SOL_SOCKET,SO_BINDTODEVICE,devname,strlen(devname))==-1) {
            perror("setsockopt() for SO_BINDTODEVICE error");
            close(sData.descriptor);
            exit(EXIT_FAILURE);
        }

Both will read and write using:

sendto(fd,packet,packetsize,0,(struct sockaddr *)&(inaddr),sizeof(inaddr))

And:

struct sockaddr_in srcAddr;
socklen_t srcAddrLen=sizeof(srcAddr);

// .....

recvfrom(fd,packet,MAX_PACKET_SIZE,0,(struct sockaddr *)&srcAddr,&srcAddrLen);

The problem is that client and server cannot communicate and the client, for each packet sent, seems to always receive a "port unreachable" ICMP packet (I can clearly see, in Wireshark, the client sending correct UDP packets and the server refusing them with "port unreachable").

Probably I'm not using the UDP socket in a proper way: do you know what I am missing here? My final goal would be to:

  • Bind the socket to a specific port both client-side and server-side, i.e. the client should send packets with the specified port as destination and the server should receive them listening exactly on the same port
  • Bind the socket only to a wireless interface (whose name is, at the moment, stored in devname - but it shouldn't be a problem to get its IP address or MAC address too)
  • Make server and client communicate through UDP, with the client sending requests, the server receiving them and replying to the client, which should receive all the replies
es483
  • 361
  • 2
  • 16
  • 3
    The program that receives data need to [bind](http://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html) the socket to an IP-address/port pair. Otherwise the system won't know what interface and port the application want to receive packets on. – Some programmer dude Jan 25 '19 at 16:58
  • If the server is rejecting the incoming packets, then the first thing to check is that the server is actually running and listening to the appropriate port. The next is the possibility that the packets are being blocked by a firewall. – John Bollinger Jan 25 '19 at 16:59
  • Do note, too, that if they are using the same address and port, then client and server cannot both run on the same machine at the same time. It is more usual for only the server to use a well-known port, with the client using whatever port it happens to like. The server then sends responses to whatever port the client's request came from. – John Bollinger Jan 25 '19 at 17:02
  • @Someprogrammerdude Thank you very much for your suggestion. I'm starting to get a little confused: is it correct that the call to `setsockopt(SO_BINDTODEVICE)` is not sufficient to bind to specific interface due to the fact that no ports are specified? Then, what can I do to obtain the desired result? Should I set: `inaddr.sin_addr.s_addr=;`, then `bind()`, then change `inaddr.sin_addr.s_addr=;`? Isn't there a way to obtain the same result without writing two times into `sin_addr.s_addr`? – es483 Jan 25 '19 at 17:33
  • 2
    I have never used that API before, but it only seems to bind the socket to an interface, the port isn't bound. Skip that socket option and use only `bind`, the operating system will bind it to the correct interface matching the address. – Some programmer dude Jan 25 '19 at 17:35
  • I've double checked, but the port is actually not blocked by any firewall, as I've used it multiple times before. Following the approach I wrote in the previous comment, I was able to make the server work: it is actually replying to the client. My doubt is now whether a better approach could be used... do I always need to use different `struct sockaddr_in`, one for `bind` and one for `sendto`? Can't I obtain the same result using only one structure? – es483 Jan 25 '19 at 18:00
  • Moreover, when selecting for instance '7000' as port, I'm actually obtaining UDP packets going from port 7000 (as source) to port 7000 (as destination)... as @JohnBollinger suggested, it would be better to have whatever port client-side and port 7000 server-side... Is there a way to obtain this result with the single socket constraint, taking into account that both client and server should receive and send data (in some cases, at the same time)? Sorry for the long comments, bit I'm a little confused about this and I wasn't able to find much useful documentation. Thanks! – es483 Jan 25 '19 at 18:04

2 Answers2

1

Although it is not clear from the question itself, your comments seem to indicate that you are not bind()ing an address to the socket, as @Someprogrammerdude inferred. In that case, it is important to understand that bind()ing serves a different and largely orthogonal purpose to that served by the SO_BINDTODEVICE socket option, the use of "BIND" in the option name notwithstanding.

The bind() function is about associating the socket with an address, which for TCP and UDP includes a port number. The SO_BINDTODEVICE is about limiting the socket to data passing through a particular device. Although in practice, there is usually a one-to-one mapping between IP addresses and network interfaces,

  1. The POSIX system interfaces are not specific to the IP protocol suite, and they take care to avoid assuming that address families all have characteristics similar to those of IP.

  2. Even for IP, it is possible for one network interface to have multiple addresses.

  3. For IP in particular, you in any case need to associate your socket with a port before the system will accept inbound traffic at that port. The socket option does not do that, nor does it directly even associate your socket with an IP address. That's the role of bind().

In comments you ask

do I always need to use different struct sockaddr_in, one for bind and one for sendto? Can't I obtain the same result using only one structure?

You can reuse the socket address structure, but be well aware that its contents need to be different for bind() than for sendto(). The former requires a local address to bind to, whereas the latter requires the remote address to which the message should be dispatched. I think it's a bit cleaner to use separate objects for those distinct purposes, but it's not necessary.

As for letting the client choose its own port, as I said in comments, that's the usual mode of operation for UDP. So usual, in fact, that the system will take care of it for you on the first call to sendto() if you have not already bound the socket. You are already using recvfrom(), by which the server (and the client, too) can obtain the address of the peer that sent each message. You should be able to feed that address object back to sendto() to send a response. Thus, it should be as easy as ensuring that the server bind()s in order to listen to a well-known port number, but the client does not, in order to use a port number assigned automatically by the system.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
1

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.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • Thank you very much for the suggestion about using ancillary messages: it's a field I don't know very much, but it can enable a lot of useful applications and I should definitely delve more into this! I just tested your code and I can confirm that it works fine. I have only few small doubts about it, which are better detailed in the next comments. – es483 Jan 29 '19 at 10:11
  • 1) Why the information received as ancillary data can be unaligned? As the same information is actually `memcpy`-ed again inside the response ancillary data, why is it needed to perform two consecutive sets of calls to `memcpy()`? – es483 Jan 29 '19 at 10:11
  • 2) Then, when printing the payload, you are checking `if ((i & 15) == 0)`: if I understood it correctly, it is a very interesting fast way of doing `(i % 16) == 0`... do you know if this will always result in faster code, or if `gcc` already optimizes a `%` operation, for instance when selecting `-O2`? – es483 Jan 29 '19 at 10:11
  • 3) Another small doubt is related to the part in which you send the response: why are you using a do/while loop? I'm very probably missing something, but isn't `sendmsg()` supposed to send all the message all at once (since there's only one iovec, too), just like `sendto()` with a specified buffer? – es483 Jan 29 '19 at 10:12
  • 4) Finally, is the choice of the `payload` and `ancillary` buffer sizes to `4096` and `1024` arbitrary or does it come from additional considerations you did (e.g. maximum size of ancillary data)? Thank you very much in advance! – es483 Jan 29 '19 at 10:12