1

I'm having issues implementing a C UDP socket program. The code below works perfectly with any input shorter than 56 characters, but if I feed it 56 characters or more, sendto complains that I gave it invalid arguments (error code 22). For instance, this will send correctly:

 ./talkerDemo localhost qqqqqwwwwweeeeeqqqqqwwwwweeeeeqqqqqwwwwweeeeeqqqqqwwwww

But this won't:

 ./talkerDemo localhost qqqqqwwwwweeeeeqqqqqwwwwweeeeeqqqqqwwwwweeeeeqqqqqwwwwwH

What gives?

/*
** talker.c
** Adapted from http://beej.us/guide/bgnet/html/single/bgnet.html#datagram
*/

#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 "4242"    // the port users will be connecting to

int main(int argc, char *argv[])
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;

    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;
    }



//============================================================
// !!!!!!! Eror occurs here:   
    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]);
    close(sockfd);

    return 0;
}

EDIT

This was the version of the code I was actually running. Before posting the question, I had gone back to the original (above) to see if the problem occurred with that implementation too - and I seemed it was. But it turns out I was being thick and was using the wrong binary... derp

/*
** UDPTalker.hpp -- a datagram sockets "server"
** Adapted from http://beej.us/guide/bgnet/html/single/bgnet.html#datagram
*/

#ifndef UDPTALKER_H
#define UDPTALKER_H

#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 <iostream>
#include <string>


#define UDPT_DEFAULT_PORT "4243"
#define UDPT_DEFAULT_HOST "localhost"

#define UDPT_MAXBUFLEN 2048


class UDPTalker {
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;

    std::string host;
    std::string port;

    public:
        //! Takes target hostname/ip and port as arguments. Defaults: ("localhost", "4243")
        UDPTalker(std::string host = UDPT_DEFAULT_HOST, std::string port = UDPT_DEFAULT_PORT);

        ~UDPTalker();

        void send(std::string msg);
};

#endif // UDPTALKER_H

Here's the corresponding .cpp:

// File UDPTalker.cpp

#include "UDPTalker.hpp"

UDPTalker::UDPTalker(std::string h, std::string port) : host(h), port(port) {
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((rv = getaddrinfo(host.c_str(), port.c_str(), &hints, &servinfo)) != 0) {
        throw std::runtime_error(std::string("getaddrinfo: ").append(gai_strerror(rv)));
    }

    // 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;
    }

    freeaddrinfo(servinfo);

    if (p == NULL) {
        throw std::runtime_error("talker: failed to create socket\n");
    }
}

UDPTalker::~UDPTalker() {
    close(sockfd);
}

void UDPTalker::send(std::string msg) {

    if ((numbytes = sendto(sockfd, msg.c_str(), msg.size(), 0,
             p->ai_addr, p->ai_addrlen)) == -1) {
        perror("talker: sendto! ");
    }

    // printf("talker: sent %d bytes to %s\n", numbytes, host.c_str());
}
Sean Bone
  • 3,368
  • 7
  • 31
  • 47
  • Error code 22 is `EINVAL`. Per the `sendto()` documentation, `EINVAL` only occurs when: "*The `dest_len` argument is not a valid length for the address family*", which is not the case here (since `getaddrinfo()` should always provide a valid address length). So, short of a buffer overflow that corrupts the `ai_addrlen`, there is no other way that the *length* of `argv[2]` would cause an `EINVAL` error, especially at such low lengths that you are working with (if you were sending more data than could fit in a single UDP datagram, you would get an `EMSGSIZE` error code instead). – Remy Lebeau Jul 12 '18 at 00:35
  • suggest reading the MAN page for `sendto()`, especially this sentence: *If the message is too long to pass atomically through the underlying protocol, the error EMSGSIZE is returned, and the message is not transmitted.* – user3629249 Jul 12 '18 at 02:37
  • 1
    have you done any debugging, like displaying the values in: `p->ai_family` `p->ai_socktype`, and `p->ai_protocol` to see what, exactly, what type of socket is being generated? – user3629249 Jul 12 '18 at 02:44
  • I seem to be able to compile and run the code without any issues with both input! `uname -a Linux 94590e76f22f 4.13.0-1011-gcp #15-Ubuntu SMP Mon Feb 12 16:29:04 UTC 2018 x86_64 GNU/Linux` – fnisi Jul 12 '18 at 02:51
  • Thanks for all your help! I eventually found my silly mistake. The code I was actually using was adapted from the one I sent above. It was integrated into a `C++` class and it seems `freeaddrinfo` was happening before `sendto`, invalidating `p`. But before posting the question, I reverted to the original version of the code (the one I posted), and was getting the same error. Turns out my brain wasn't on and I was using the wrong binary. *derp* – Sean Bone Jul 12 '18 at 09:49

1 Answers1

0

freeaddrinfo(servinfo); frees up the memory used by servinfo. This means that the pointer p is now pointing to empty memory, so when it gets passed to sendto it may have invalid content. My guess is that for some reason the extra byte in the input string was "rolling over" that memory location when the class' send method was being called. The fix was moving freeaddrinfo(servinfo); from the constructor to the destructor:

UDPTalker::UDPTalker(std::string h, std::string port) : host(h), port(port) {
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((rv = getaddrinfo(host.c_str(), port.c_str(), &hints, &servinfo)) != 0) {
        throw std::runtime_error(std::string("getaddrinfo: ").append(gai_strerror(rv)));
    }

    // 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) {
        throw std::runtime_error("talker: failed to create socket\n");
    }
}

UDPTalker::~UDPTalker() {
    freeaddrinfo(servinfo);
    close(sockfd);
}
Sean Bone
  • 3,368
  • 7
  • 31
  • 47