25

I found the following code to open a connection in C:

int OpenConnection(const char *hostname, int port)
{
    int sd;
    struct hostent *host;
    struct sockaddr_in addr = {0};
    if ((host = gethostbyname(hostname)) == NULL)
    {
        perror(hostname);
        abort();
    }

    sd = socket(PF_INET, SOCK_STREAM, 0);

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = *(long *)(host->h_addr_list[0]);
    if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
    {
        close(sd);
        perror(hostname);
        abort();
    }
    return sd;
}

As I was rewriting this code in C++, I found out that I should be using getaddrinfo instead of gethostbyname, according to a comment in this post: C - What does *(long *)(host->h_addr); do?.

I was looking at Beej's guide to socket programming, and found the following example:

int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // will point to the results

memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC;     // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
    exit(1);
}

// servinfo now points to a linked list of 1 or more struct addrinfos

// ... do everything until you don't need servinfo anymore ....

freeaddrinfo(servinfo); // free the linked-list

However, this example applies to creating a server, which makes me wonder

A] is getaddrinfosupposed to be used for clients

and

B] How would I modify the hints struct and the function parameters in order to make this code suitable for creating a client?

Currently, I have the following code but I

struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

struct addrinfo *client_info;

const int status = getaddrinfo(hostname, port, &hints, &client_info); //don't know if its correct

Update:

For anyone who may find it useful, here is my final code after reading the accepted answer:

int OpenConnection(const char *hostname, const char *port)
{
    struct hostent *host;
    if ((host = gethostbyname(hostname)) == nullptr)
    {
        //More descriptive error message?
        perror(hostname);
        exit(EXIT_FAILURE);
    }

    struct addrinfo hints = {0}, *addrs;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    const int status = getaddrinfo(hostname, port, &hints, &addrs);
    if (status != 0)
    {
        fprintf(stderr, "%s: %s\n", hostname, gai_strerror(status));
        exit(EXIT_FAILURE);
    }

    int sfd, err;
    for (struct addrinfo *addr = addrs; addr != nullptr; addr = addr->ai_next)
    {
        sfd = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
        if (sfd == ERROR_STATUS)
        {
            err = errno;
            continue;
        }

        if (connect(sfd, addr->ai_addr, addr->ai_addrlen) == 0)
        {
            break;
        }

        err = errno;
        sfd = ERROR_STATUS;
        close(sfd);
    }

    freeaddrinfo(addrs);

    if (sfd == ERROR_STATUS)
    {
        fprintf(stderr, "%s: %s\n", hostname, strerror(err));
        exit(EXIT_FAILURE);
    }

    return sfd;
}
Foobar
  • 7,458
  • 16
  • 81
  • 161
  • 1
    A) `getaddrinfo` is fine for use in a client. B) `port` will have to be a `char` array now because `getaddrinfo` is smart enough to eat input like "ftp" and convert that to the appropriate number. Rest looks good. – user4581301 Oct 09 '18 at 19:04
  • 2
    what is "client" or "server" ? `getaddrinfo` translate host name to address. this is absolute unrelated to what your code will be do next – RbMm Oct 09 '18 at 19:10

2 Answers2

19

The gethostbyname() and gethostbyaddr() functions are deprecated on most platforms, and they don't implement support for IPv6. IPv4 has reached its limits, the world has been moving to IPv6 for awhile now. Use getaddrinfo() and getnameinfo() instead, respectively.

To answer your questions:

A. getaddrinfo() and getnameinfo() can be used for clients and servers alike, just as gethostbyname() and gethostbyaddr() can be. They are just host/address resolution functions, how the resolved values get used is up to the calling app to decide.

B. client code using getaddrinfo() would look something like this:

int OpenConnection(const char *hostname, int port)
{
    int sd, err;
    struct addrinfo hints = {}, *addrs;
    char port_str[16] = {};

    hints.ai_family = AF_INET; // Since your original code was using sockaddr_in and
                               // PF_INET, I'm using AF_INET here to match.  Use
                               // AF_UNSPEC instead if you want to allow getaddrinfo()
                               // to find both IPv4 and IPv6 addresses for the hostname.
                               // Just make sure the rest of your code is equally family-
                               // agnostic when dealing with the IP addresses associated
                               // with this connection. For instance, make sure any uses
                               // of sockaddr_in are changed to sockaddr_storage,
                               // and pay attention to its ss_family field, etc...
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    sprintf(port_str, "%d", port);

    err = getaddrinfo(hostname, port_str, &hints, &addrs);
    if (err != 0)
    {
        fprintf(stderr, "%s: %s\n", hostname, gai_strerror(err));
        abort();
    }

    for(struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next)
    {
        sd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
        if (sd == -1)
        {
            err = errno;
            break; // if using AF_UNSPEC above instead of AF_INET/6 specifically,
                   // replace this 'break' with 'continue' instead, as the 'ai_family'
                   // may be different on the next iteration...
        }

        if (connect(sd, addr->ai_addr, addr->ai_addrlen) == 0)
            break;

        err = errno;

        close(sd);
        sd = -1;
    }

    freeaddrinfo(addrs);

    if (sd == -1)
    {
        fprintf(stderr, "%s: %s\n", hostname, strerror(err));
        abort();
    }

    return sd;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for the help! I have a few questions, however I understand they may be out of the scope of my original question, so feel free to not answer. Is `sd` shorthand for "socket descriptor"? Also why does `getaddrinfo` accept a pointer to a pointer in the form of `&addrs`. From my understanding, `getaddrinfo` returns a linked list of pointers in the form of a pointer to the first node in the linked list. However, I do not understand why creating a linked list of pointers requires knowing the pointer to the pointer of the first node in the linked list. – Foobar Oct 09 '18 at 22:44
  • @Roymunson "*Is `sd` shorthand for "socket descriptor"?*" Most likely. "*why does `getaddrinfo` accept a pointer to a pointer in the form of `&addrs`*" Because it returns the address of allocated memory to the caller. `&addrs` passes in the address of `addrs` itself so `getaddrinfo()` can assign the allocated memory address to `addrs`. – Remy Lebeau Oct 09 '18 at 22:53
  • @Roymunson "*`getaddrinfo` returns a linked list of pointers in the form of a pointer to the first node in the linked list*" It is an output parameter. C uses a pointer for that. "*I do not understand why creating a linked list of pointers requires knowing the pointer to the pointer of the first node*" How else do you think `getaddrinfo()` gives you a pointer to the first node? It can't use the return value, that is already used for returning an error code. So an output parameter is used, and that requires passing in a pointer to a pointer-variable that receives the address of the first node. – Remy Lebeau Oct 09 '18 at 22:54
  • Thanks for the quick reply. My last question is, your for-loop will abort if there is a failure in creating a socket, but it will continue to the next node in the linked list if there is a failure in connecting. Why does it also not continue if there is a failure in creating the socket? – Foobar Oct 09 '18 at 23:00
  • 1
    @Roymunson Because in my example, using `AF_INET` in the `hints`, the parameter values passed to `socket()` will not change on each iteration of the loop, so if `socket()` fails once, it will likely fail again on subsequent iterations, thus the loop is stopped on the first failure. If you use `AF_UNSPEC` instead, it would make more sense to keep trying, as the paramter values may be different, alternating between `AF_INET` and `AF_INET6`. – Remy Lebeau Oct 09 '18 at 23:05
  • @Roymunson A failed `connect()`, on the other hand, is a recoverable error, by simply closing the current socket and creating a new socket on the next loop iteration for a new `connect()` attempt. – Remy Lebeau Oct 09 '18 at 23:06
  • Just a little nitpick, but `freeaddrinfo(&addrs);` should be `freeaddrinfo(addrs);` – niraami Oct 15 '19 at 20:59
8

I've always used gethostbyname() since "forever". It's always worked, it continues to work, and it's "simpler".

getaddrinfo() is the newer function:

http://man7.org/linux/man-pages/man3/getaddrinfo.3.html

The getaddrinfo() function combines the functionality provided by the gethostbyname(3) and getservbyname(3) functions into a single interface, but unlike the latter functions, getaddrinfo() is reentrant and allows programs to eliminate IPv4-versus-IPv6 dependencies.

I understand that getaddrinfo() ismore robust, more efficient, and more secure: You shouldn't be using gethostbyname() anyway

ADDENDUM:

In reply to your specific questions:

A] getaddrinfo() is preferred over gethostbyname() to lookup the IP address of a hostname; either "client" or "server".

B] Q: How would I modify the hints struct and the function parameters?

A: The "hints" look OK, but I would probably modify the port to NULL.

Here's a complete example:

https://www.kutukupret.com/2009/09/28/gethostbyname-vs-getaddrinfo/

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

int main(int argc, char *argv[])
{
    struct addrinfo hints, *res, *p;
    int status;
    char ipstr[INET6_ADDRSTRLEN];

    if (argc != 2) {
       fprintf(stderr, "Usage: %s hostname\n", argv[0]);
       return 1;
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
    hints.ai_socktype = SOCK_STREAM;

    if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
        return 2;
    }

    for(p = res;p != NULL; p = p->ai_next) {
        void *addr;
        if (p->ai_family == AF_INET) {
            return 1;  
        } else {
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
            addr = &(ipv6->sin6_addr);

            /* convert the IP to a string and print it: */
            inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);             
            printf("Hostname: %s\n", argv[1]);
            printf("IP Address: %s\n", ipstr);
        }
    }

    freeaddrinfo(res); // free the linked list     
    return 0;
}
paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • Well...you copy/pasted the same section I was about to and the trick here is `getservbyname` which returns an entry from the services. This "implies" this method is a service to be used for servers and not clients. – jiveturkey Oct 09 '18 at 19:07
  • 2
    No, the impression I wanted to convey is simply that `getaddrinfo()` is a "better" way to map hostname to IP than the classic `gethostbyname()`. Both functions are applicable to *EITHER* client or server. – paulsm4 Oct 09 '18 at 19:09
  • 3
    @jiveturkey `getservbyname()` is just a function to map symbolic "service names" such as `http` or `ftp` to port numbers, it has nothing to do with whether you're running a client or a server. It's probably just named that way because on Unix systems it reads `/etc/services` which specifies the mapping. – Daniel Schepler Oct 09 '18 at 19:47
  • "*I've always used `gethostbyname()` since "forever". It's always worked, it continues to work, and it's "simpler".*" - it is also not compatible with IPv6, which is the main reason `getaddrinfo()` was created to replace it. – Remy Lebeau Oct 09 '18 at 19:49
  • I'm surprised the port should be null - after all, I am creating a client program that is trying to connect to a server running on a specific port. – Foobar Oct 09 '18 at 21:54
  • @Roymunson the example given is not connecting to a server, merely resolving the hostname's IP addresses and printing them, so the port does not matter. Obviously, if you wanted to bind a socket to a port, or connect a socket to a port, you would not pass a NULL port to `getaddrinfo()`, unless you are prepared to manually assign the port to the resulting `sockaddr_in/6` structs after `getaddrinfo()` exits. Better to just give the port to `getaddrinfo()` and let it populate the `sockaddr_in/6` structs for you. – Remy Lebeau Oct 09 '18 at 22:57
  • This is pretty dated, but I have no choice, but to ask this way.I want to use getaddrinfo to get REMOTE bluetooth device data. All I am getting is " Name or service not known" after I supply remote BT device name. Isgetaddrinfo usable for bluetooth and how? Won't work on local BT device either. –  Mar 02 '20 at 03:48