Sockets sendto() returning EINVAL

2020-07-27 03:22发布

I'm trying to send a UDP packet in C. I have the following sendto():

char* msg = "Hello";

//ret is the return value of getaddrinfo, the address is AF_INET (IPv4)
//and the sock_type is SOCK_DGRAM (UDP)
struct sockaddr_in *ip = (struct sockaddr_in *)ret->ai_addr;

if ((sendto(sock, msg, strlen(msg), 0, (struct sockaddr *)ip, 
                sizeof(struct sockaddr *))) != -1) {
    printf("msg sent successfully");
} else {
    printf("Error sending msg: %s\n", strerror(errno));
}

However, it's returning an error saying there's an invalid argument. Looking at the manpage I can't really tell which one is the invalid argument. Any ideas?

EDIT: Here's all my code

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

int main(int argc, const char *argv[])
{
    /*
     * Help the technically challenged among us who have no idea
     * what on God's green Earth they are doing with this thing.
    */
    if (argc != 2) {
        printf("usage: routetracer <ip address or hostname>\n");
        return -1;
    }

    /*
     * hints- parameters for return value of getaddrinfo
     * ret- return value of getaddrinfo
     */
    struct addrinfo hints, *ret;
    int status;
    char ipv4[INET_ADDRSTRLEN];
    int ttl = 0;
    char* msg = "Hello";
    int last_hop = 0;

    //define what we want from getaddrinfo
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET; //IPv4
    hints.ai_socktype = SOCK_DGRAM; //UDP packets

    //call getaddrinfo to fill ret, w/ error chk
    if ((status = getaddrinfo(argv[1], NULL, &hints, &ret)) != 0) {
        printf("getaddrinfo: %s\n", gai_strerror(status));
        return -1;
    }

    //extract IPv4 address from ret
    struct sockaddr_in* ip = (struct sockaddr_in *)ret->ai_addr;

    //convert address from pure numbers to something easier to read
    inet_ntop(ret->ai_family, &(ip->sin_addr), ipv4, INET_ADDRSTRLEN);   

    //kindly inform the user of which hostname they are connecting to
    printf("Route for: %s\n", ipv4);

    //create a socket
    int sock = socket(ret->ai_family, ret->ai_socktype, ret->ai_protocol);

    ttl = 1;
    if ((setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))) != -1) {
        printf("TTL set successfully\n");
    } else {
        printf("Error setting TTL: %s\n", strerror(errno));
    }

    if ((sendto(sock, msg, strlen(msg), 0, ret->ai_addr,
                    ret->ai_addrlen)) != -1) {
        printf("msg sent successfully");
    } else {
      printf("Error sending msg: %s\n", strerror(errno));
    }
    return 0;
}

Running the program gives the following output:

$ ./routetracer www.google.com
Route for: 173.194.46.82
TTL set successfully
Error sending msg: Invalid argument

2条回答
看我几分像从前
2楼-- · 2020-07-27 04:06

Try:

if ((sendto(sock, msg, strlen(msg), 0, (struct sockaddr *)ip, 
                sizeof(struct sockaddr_in))) != -1) {

You're giving it the size of a pointer, not the size of the structure. And it needs to be the specific structure type, not the generic type.

查看更多
啃猪蹄的小仙女
3楼-- · 2020-07-27 04:24

As Barmar points out, one reason for the EINVAL is the incorrect:

  sizeof(struct sockaddr *)

which gives the size of a pointer. See Socket programming: sendto always fails with errno 22 (EINVAL).

The second reason seems to be sin_port, which getaddrinfo returns as 0. Changing it to 80 say clears up the EINVAL, as in:

  ((struct sockaddr_in *)ret->ai_addr)->sin_port = htons(80); // test

Here port 80 here is not HTTP, but instead (for UDP) is Google's experimental QUIC Chromium.

Wikipedia http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers states that Port 0 is for UDP reserved, and for TCP is unofficially reserved as a "programming technique for specifying system-allocated (dynamic) ports".

And as an aside (and referring to the original question), you may not need bother with the variable ip. You are casting ret->ai_addr to struct sockaddr_in *, and then back again to its original type.

And, as Remy Lebeau points out, it is better to use the service parameter of getaddrinfo. So putting this all together, your code could look more like:

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_INET; //IPv4
  hints.ai_socktype = SOCK_DGRAM; //UDP packets
  if ((status = getaddrinfo(argv[1], "80", &hints, &ret)) != 0) {
      printf("getaddrinfo: %s\n", gai_strerror(status));
      return -1;
  }
  assert(ret->ai_family   == AF_INET);     // guaranteed
  assert(ret->ai_socktype == SOCK_DGRAM);  // guaranteed
  assert(((struct sockaddr_in *)ret->ai_addr)->sin_port == htons(80)); // guaranteed
  // ...
  if ((sendto(sock, msg, strlen(msg), 0, ret->ai_addr, ret->ai_addrlen)) != -1) {
  // ...
查看更多
登录 后发表回答