recvfrom - Obtaining the Return Address

· March 26, 2022

Report a bug

Have you ever wondered how to obtain the IP address from a client when working on a socket application? That is what recvfrom() API comes in handy.

tldr
recvfrom() requires src_addr and addrlen to not be null and addrlen to be initialized with the size of `struct sockaddr`

From my limited experience with sockets, I have always used the read() API to receive messages from the client or server. However, there are various methods to read messages from a socket such as recv() and recvfrom(). The manpages provide an excellent summary of the differences between read() and recv():

The only difference between recv() and read(2) is the presence of flags.
With a zero flags argument, recv() is generally equivalent to read(2)

Essentially recv() provides more control on how to behave such as to not ensure the call is non-blocking with MSG_DONTWAIT flag. This can be useful if you only want to set certain behaviors/features per-call rather than applying the behavior on the entire socket (i.e fcntl()).

Anyhow, to retrieve the IP address of the sender, you will need to use recvfrom() rather than recv() call since you pass in a pointer where the API will set the IP address of the sender (src_addr) and the size in addrlen.

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags,
                 struct sockaddr *restrict src_addr,
                 socklen_t *restrict addrlen);

Since src_addr and addrlen are both pointers, to obtain the source address, both parameters need to be not NULL. However, recvfrom() will not simply work as intended if you do not initialize addrlen as seen from the output:

Snippet printf("(address: %s) %s\n", inet_ntoa(from.sin_addr), buf);

Output:

(address: 0.0.0.0) Hello World

recvfrom() API did not populate src_addr (from in my code snippet) with the correct IP address because I did not initialize addrlen parameter with the size of src_addr (i.e. sizeof(addrlen)).

Output:

(address: 127.0.0.1) Hello World

Initializing addrlen With Different Sizes

The standard practice is to initialize addrlen with the size of struct sockaddr which happens to be 16 bytes on my machine. However, you could set the value to be less than 16 bytes and it may still work.

If you set the length to be less than 0, an errno will be set complaining that an invalid argument has been supplied.

recvfrom(): Invalid argument

This is because addrlen is of type `unsigned int which we can verify by running the preprocessor:

$ gcc -E server-recvfrom.c  | grep socklen_t
typedef unsigned int __socklen_t;
typedef __socklen_t socklen_t;

Your compiler would have caught this error if you have declared src_addr as of type socklen_t instead of int if you have -Wsign-conversion flag enabled (at least for gcc). What happens if you provide the length to a value less than sizeof(struct sockaddr) such as 0 bytes? The source address will not filled if the value of addrlen is too small. Then what would be the minimum length that can be set to addrlen for recvfrom to populate the source IP address?

The answer lies in examining sockaddr_in structure (or you can brute-force which I found was to be 8 bytes on my environment):

$ gcc server-recvfrom.c -E | grep -A 11 -E "struct sockaddr_in$"
struct sockaddr_in
  {
    sa_family_t sin_family;
    in_port_t sin_port;
    struct in_addr sin_addr;


    unsigned char sin_zero[sizeof (struct sockaddr)
      - (sizeof (unsigned short int))
      - sizeof (in_port_t)
      - sizeof (struct in_addr)];
  };

A struct from my understanding is similar to an array in the sense that all members in the struct are stored in a contiguous chunk of memory. This means that you can address a member in a struct without using the dot nor the arrow operator (i.e. person.age or person->age). This can be achieved by knowing the size of each member (also taking into account of padding for alignment reasons). For instance, let’s define a struct as the following:

struct Person {
  int age;
  char name[16];
}

where the size of the struct is 20 bytes:

  • 4 bytes to represent the integer
  • 16 bytes to represent a character array of 16 characters

Although I am not familiar with how to access structs using pointers, a method I came up with is using memcpy to access members I desire.

Code:

struct Person human = { 21, "John Smith" };                                   
int age = 0;                                                                  
memcpy(&age, &human, sizeof(int));                                            
printf("the person age is: %d\n", age);  

Output:

the person age is: 21

Given the fact that a struct is just a contiguous chunk of memory that consists of members of different sizes, we can estimate the minimum amount of memory required to set addrlen to obtain the IP address from recvfrom()

Recall src_addr is the type struct sockaddr_in:

struct sockaddr_in                                                              
  {                                                                             
    sa_family_t sin_family;                                                     
    in_port_t sin_port;                                                         
    struct in_addr sin_addr;   

We can see that to represent the IP address (sin_addr), we need at least sizeof(sa_family_t) + sizeof(in_port_t) + sizeof(struct in_addr) which happens to be 8 bytes on my machine because sa_family_t is a typedef of short unsigned int (2 bytes), sin_port_t is a typedef of uint16_t (16 $\require{cancel}\bcancel{bits} \cdot \frac{bytes}{8\bcancel{bits}} = 2$ bytes) and struct in_addr is simply a struct that contains only one member of type in_addr_t which is simply a typdef of uint32_t (4 bytes). This happens to be the same number of bytes as what I found through brute-force.

$ gcc server-recvfrom.c -E | grep  -E "sa_family_t;"
typedef unsigned short int sa_family_t;
$ gcc server-recvfrom.c -E | grep -E "in_port_t;$"
typedef uint16_t in_port_t;
$ gcc server-recvfrom.c -E | grep -E -A 3 "struct in_addr$"
struct in_addr
  {
    in_addr_t s_addr;
  };
$ gcc server-recvfrom.c -E | grep -E  "in_addr_t;"
typedef uint32_t in_addr_t;

You may be asking why do we pass addrlen as a pointer and not as an unsigned integer. The reason can be found in the manpages: Upon return, addrlen is updated to contain the actual size of the source address. So addrlen will not have the size of 8 bytes or whatever bytes you have set it to be. Does this mean that the value of addrlen matter? Yes it does because recvfrom() will only write up to addrlen bytes to src_addr as seen in the code.

/* Read N bytes into BUF through socket FD.
   If ADDR is not NULL, fill in *ADDR_LEN bytes of it with tha address of
   the sender, and store the actual size of the address in *ADDR_LEN.
   Returns the number of bytes read or -1 for errors.  */

Conclusion

To summarize this long blog post, use recvfrom() API to retrieve the source IP address (if the protocol supports it). Ensure that you have passed non-NULL values for src_addr and addrlen arguments with addrlen initialized to sizeof(struct sockaddr_in) to obtain all the necessary information.

Twitter, Facebook