Search code examples
csocketsudpbindgetaddrinfo

Send UDP packet with fixed source port number using getaddrinfo and bind


Using BJ's talker.c code as a template: http://beej.us/guide/bgnet/examples/talker.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define SERVERPORT "4950"    // the port users will be connecting to

int main(int argc, char *argv[])
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;
    struct sockaddr_storage their_addr;
    socklen_t addr_len;
    addr_len = sizeof their_addr;

    if (argc != 3) {
        fprintf(stderr,"usage: talker hostname message\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and make a socket
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("talker: socket");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "talker: failed to create socket\n");
        return 2;
    }

    if ((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0,
             p->ai_addr, p->ai_addrlen)) == -1) {
        perror("talker: sendto");
        exit(1);
    }

    freeaddrinfo(servinfo);

    printf("talker: sent %d bytes to %s\n", numbytes, argv[1]);


//============== Added Code for recvfrom() (pseudocode-ish) =============


    if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN , 0, (struct sockaddr *)&their_addr, &addr_len)) == -1) 
    {
        close(sockfd);
        perror("talker: recvfrom");    
        exit(1);
    }

    close(sockfd);

    printf("Got packet\n");

//============== End Added Code for recvfrom() =============


    close(sockfd);

    return 0;
}

I have a requirement whereby the client UDP process that talks to the server must use a fixed, known source port number. In this case, assume it's SERVERPORT (4950). The server then responds to that port number. Yes, this is unusual as most servers respond to the ephemeral port number that the system assigns to the sender.

After sending a packet using sendto(), I listen for a response using recvfrom(). That's the (pseudo)code I added in the above example.

All my searches online point to using bind() but that code is usually on the server side. I haven't found a way to bind on the client side using the modern getaddrinfo() method. I tried to add a bind() right after the socket() setup but that wouldn't work because p is a server-side structure (derived from the hints structure that uses the server IP address) and I get a bind Error:

Error 99 (Cannot assign requested address)

code added:

bind(sockfd, p->ai_addr, p->ai_addrlen)

I want to do this in a way that will work for both IPv4 and IPv6.

I've seen other examples whereby a local/source sockaddr_in structure is filled out with the client's information and that is used in the bind, but those are IPv4 or IPv6 specific.

Can someone please show me how to properly update the talker.c code to sendto() and recvfrom() a UDP server using a fixed source port number? Assume that the server is immutable.


Solution

  • The server then responds to that port number. Yes, this is unusual

    There is nothing unusual about that. This is how most UDP servers are meant to work. They always respond to the sender's port. They have no concept whether that port is fixed or ephemeral, that is for the sender to decide. Unless a particular protocol dictates that responses are to be sent to a different port, which is not common.

    All my searches online point to using bind()

    Correct, that is what you need in this situation.

    but that code is usually on the server side.

    There is nothing preventing a client from using bind().

    I haven't found a way to bind on the client side using the modern getaddrinfo() method.

    It is the exact same as on the server side, except that you have to bind to a specific IP address, you can't bind to 0.0.0.0 or ::0 like you can with a server socket.

    I tried to add a bind() right after the socket() setup but that wouldn't work

    Yes, it does. The problem is that you are using the SAME IP address for both binding and sending, and that will not work. You need to bind to the CLIENT's IP address and then send to the SERVER's IP address.

    because p is a server-side structure (derived from the hints structure that uses the server IP address)

    You are misusing p. You can't bind() a client socket to the server's IP address (you need to use connect() for that instead). You need to bind() a client socket to an IP address that is local to the client's machine. Just like you have to bind() a server socket to an IP address that is local to the server machine.

    Remember, a socket is associated with a pair of IP addresses. bind() establishes the socket's LOCAL IP address. connect() establishes the socket's REMOTE IP address.

    I want to do this in a way that will work for both IPv4 and IPv6.

    You can't create a single client socket for both protocols. You need separate sockets for each protocol (on the server side, you can create a single socket for both protocols, if your platform supports dual-stack sockets).

    I've seen other examples whereby a local/source sockaddr_in structure is filled out with the client's information and that is used in the bind, but those are IPv4 or IPv6 specific.

    Yes, because you will be sending a packet using EITHER IPv4 OR IPv6, you can't send a packet using both protocols at the same time (a dual-stack socket can receive packets from either protocol, though).

    Can someone please show me how to properly update the talker.c code to sendto() and recvfrom() a UDP server using a fixed source port number . Assume that the server is immutable

    Try something like this:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <stdbool.h>
    
    #define LOCALPORT  "4950"     // the port users will be sending from
    #define SERVERPORT "4950"    // the port users will be connecting to
    #define MAXBUFLEN  65535
    
    int main(int argc, char *argv[])
    {
        int sockfd;
        struct addrinfo hints, *myinfo, *servinfo, *pserv, *plocal;
        int rv;
        int numbytes;
        char buf[MAXBUFLEN]; 
        char ipstr[INET6_ADDRSTRLEN];
        fd_set readfds;
        struct timeval tv;
        bool stop = false;
    
        if (argc < 3) {
            fprintf(stderr, "usage: talker destaddr message [localaddr]\n");
            return 1;
        }
    
        // get all of the server addresses
        memset(&hints, 0, sizeof hints);
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_DGRAM;
        hints.ai_protocol = IPPROTO_UDP;
    
        if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) {
            fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
            return 2;
        }
    
        // loop through all the server addresses
        for(pserv = servinfo; (pserv != NULL) && (!stop); pserv = pserv->ai_next) {
    
            memset(ipstr, 0, sizeof(ipstr));
            switch (pserv->ai_family)
            {
                case AF_INET:
                    inet_ntop(AF_INET, &(((struct sockaddr_in*)pserv->ai_addr)->sin_addr), ipstr, INET_ADDRSTRLEN);
                    break;
                case AF_INET6:
                    inet_ntop(AF_INET6, &(((struct sockaddr_in6*)pserv->ai_addr)->sin6_addr), ipstr, INET6_ADDRSTRLEN);
                    break;
            }
    
            printf("talker: trying to send message to %s\n", ipstr);
    
            // get all of the matching local addresses
            memset(&hints, 0, sizeof hints);
            hints.ai_family = pserv->ai_family;
            hints.ai_socktype = pserv->ai_socktype;
            hints.ai_protocol = pserv->ai_protocol;
    
            if ((rv = getaddrinfo(argc > 3 ? argv[3] : NULL, LOCALPORT, &hints, &myinfo)) != 0) {
                fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
                continue;
            }
    
            // loop through all the local addresses, sending the
            // message from each one until a reply is received
            for(plocal = myinfo; (plocal != NULL) && (!stop); plocal = plocal->ai_next) {
    
                if ((sockfd = socket(plocal->ai_family, plocal->ai_socktype, plocal->ai_protocol)) == -1) {
                    perror("socket");
                    continue;
                }
    
                memset(ipstr, 0, sizeof(ipstr));
                switch (plocal->ai_family)
                {
                    case AF_INET:
                        inet_ntop(AF_INET, &(((struct sockaddr_in*)plocal->ai_addr)->sin_addr), ipstr, INET_ADDRSTRLEN);
                        break;
                    case AF_INET6:
                        inet_ntop(AF_INET6, &(((struct sockaddr_in6*)plocal->ai_addr)->sin6_addr), ipstr, INET6_ADDRSTRLEN);
                        break;
                }
    
                printf("talker: binding to %s\n", ipstr);
    
                if (bind(sockfd, plocal->ai_addr, plocal->ai_addrlen) == -1) {
                    perror("bind");
                    close(sockfd);
                    continue;
                }
    
                // make sure this server address is the only one we talk to
                if (connect(sockfd, pserv->ai_addr, pserv->ai_addrlen) == -1) {
                    perror("connect");
                    close(sockfd);
                    continue;
                }
    
                if ((numbytes = send(sockfd, argv[2], strlen(argv[2]), 0)) == -1) {
                    perror("send");
                    close(sockfd);
                    continue;
                }
    
                printf("talker: sent %d bytes\n", numbytes);
    
                FD_ZERO(&readfds);
                FD_SET(sockfd, &readfds);
    
                tv.tv_sec = 5;
                tv.tv_usec = 0;
    
                rv = select(sockfd+1, &readfds, NULL, NULL, &tv);
                if (rv == -1)
                {
                    perror("select");
                    close(sockfd);
                    continue;
                }
    
                if (rv == 0)
                {
                    printf("talker: no reply for 5 seconds\n");
                    close(sockfd);
                    continue;
                }
    
                if ((numbytes = recv(sockfd, buf, MAXBUFLEN, 0)) == -1) 
                {
                    perror("recv");
                    close(sockfd);
                    continue;
                }
    
                printf("talker: received %d bytes\n", numbytes);
    
                close(sockfd);
    
                stop = true;
                break;
            }
    
            freeaddrinfo(myinfo);
        }
    
        freeaddrinfo(servinfo);
    
        close(sockfd);
    
        if (!stop) {
            fprintf(stderr, "talker: failed to communicate with server\n");
            return 3;
        }
    
        return 0;
    }