1

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.

Frak
  • 832
  • 1
  • 11
  • 32
  • Try: https://stackoverflow.com/questions/9873061/how-to-set-the-source-port-in-the-udp-socket-in-c and https://www.linuxquestions.org/questions/programming-9/how-to-specify-source-port-when-sending-udp-packet-484816/ – Craig Estey Sep 07 '18 at 19:15
  • Thank you but this is exactly what I mean by other examples that use specific IPv4/IPv6 constructs. I saw it before and it uses AF_NET which is for IPv4. It also doesn't use getaddr info. It's an outdated method. I'd really like to see an answer that uses the talker example since it is (mostly) IP version agnostic. – Frak Sep 07 '18 at 19:38

1 Answers1

6

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;
}
Frak
  • 832
  • 1
  • 11
  • 32
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • You don't need to specify a local IP address; first `bind` the local socket to `(INADDR_ANY, LOCALPORT)` (`in6addr_any` for v6). Then `connect` to `(server_ip, SERVERPORT)`. The kernel will choose the correct local IP address to reach that server IP address. (You can call `getsockname` on the connected socket if the client needs to know what address it's ended up using.) – Gil Hamilton Sep 07 '18 at 20:42
  • @GilHamilton that works for TCP, but not for UDP, which is connectionless. `connect()` on a UDP socket is a simple static assignment for subsequent sends/recvs, it doesn't perform a route lookup, and `getsockname()` just returns the IP that was used with `connect()` – Remy Lebeau Sep 07 '18 at 21:11
  • `Unless a particular protocol dictates that responses are to be sent to a different port, which is not common.` Yes, this is exactly what's happening. I send a packet from `myIP:random-port` and the server responds to `myIP:standard-port`. That's what I meant. It's a proprietary protocol. Thank you for the explanation. Your answer is StackOverflow GOLD! – Frak Sep 08 '18 at 13:19
  • Regarding IPv4/IPv6. I just meant that I didn't want to have IPv4 or IPv6-specific code for the destination server logic. Up until now, the IP stack handled which protocol to use for sending from when I called `sendto()`. Now, since I need to bind to a local IP address, there is the extra step of finding out what my local IP address is via the second `getaddrinfo()` call. I agree that I have to send the packet on one or the other protocol. Do I have to use `connect()` followed by `send()`? Can I use `sendto()` followed by `recvfrom()` like the example? – Frak Sep 08 '18 at 13:25
  • @GilHamilton Sorry for not following, but how do I `bind` the local socket to (INADDR_ANY, LOCALPORT)? Port number is not an argument to `bind()` nor `socket()`. In fact, I don't understand how `socket()` even knows what the port number is since it's not provided in any of the arguments. Only `getaddrinfo()` seems to get the port number. Do you mean setup a `sockaddr_in` or `sockaddr_in6` structure and populate its `sin_port` or `sin_port6` and pass that in to `bind()`? I was hoping to avoid that bit of IPv4/IPv6 ugliness. – Frak Sep 08 '18 at 14:04
  • @frakman1 you CAN omit `connect()` and use `sendto()` and `recvfrom()`, but using `connect()` is safer since you know the exact server IP/port you are sending to and expect to receive from – Remy Lebeau Sep 08 '18 at 17:08
  • @frakman1 "*I don't understand how `socket()` even knows what the port number is*" - it doesn't, that is what `bind` is for. "*Do you mean setup a `sockaddr_in` or `sockaddr_in6` structure and populate its `sin_port` or `sin_port6` and pass that in to `bind()`?*"- yes. `getaddrinfo()` gives you `sockaddr` structs that are suitable for passing to `bind()` and `connect()`. "*I was hoping to avoid that bit of IPv4/IPv6 ugliness.*" - you can't avoid it. – Remy Lebeau Sep 08 '18 at 17:12
  • @RemyLebeau The kernel does a routing lookup when you attempt the `connect`. No packet need be sent. Try this: `python -c "import socket; s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.bind(('', 9999)); s.connect(('stackoverflow.com', 80)); print(s.getsockname())"` – Gil Hamilton Sep 09 '18 at 19:52
  • @RemyLebeau Re: The last `close(sockfd)` before the `if (!stop)`. If the outer loop failed to find any working `pserv`s, then no socket will have been opened. Therefore the call to `close(sockfd)` would fail right? If so, is it a graceful failure? Perhaps it should be moved to just before the final `return 0` – Frak Sep 10 '18 at 17:48