How to choose a server socket address using getaddrinfo?

旧时模样 提交于 2019-12-12 02:36:42

问题


I would like to create a TCP server application which lets the user choose the local address that is used in the bind call. The user may provide a textual representation of a host name or IP address, so I thought of using the getaddrinfo function to translate the textual representation into one or several sockaddr structs (performing name lookup if necessary).

Now here's my problem: The getaddrinfo function does not seem to suit my needs, because it requires the AI_PASSIVE flag to be set in the hints structure in order to obtain socket addresses that may be used in bind calls. But if I use AI_PASSIVE, I can not use the nodename parameter anymore, which defeats the whole purpose of letting the user choose the local address. If I do not provide AI_PASSIVE, getaddrinfo will only return those addresses which can be used in connect, sendto and sendmsg calls, but there might be addresses that can be used for binding but not for connect, sendto or sendmsg calls, which would be omitted. See the POSIX specification regarding the getaddrinfo/freeaddrinfo functions.

To clarify my needs, here is a sketch of the application I am trying to create:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>

int main(int argc, char **argv)
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s address port\n", argv[0]);
        return EXIT_FAILURE;
    }

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

    struct addrinfo *result;
    /* The next line won't work as intended! It will behave as if the
    AI_PASSIVE flag was not set. */
    if (getaddrinfo(argv[1], argv[2], &hints, &result) != 0) {
        perror("getaddrinfo");
        return EXIT_FAILURE;
    }

    /* Iterate result list */
    int sfd;
    struct addrinfo *rp = result;
    do {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;

        if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
            break; /* Success */

        close(sfd);
        rp = rp->ai_next;
    } while (rp != NULL);

    freeaddrinfo(result);

    if (rp == NULL) { /* No address succeeded */
        fprintf(stderr, "Could not bind\n");
        return EXIT_FAILURE;
    }

    /* ... use socket bound to sfd ... */

    close(sfd);

    return EXIT_SUCCESS;
}

I actually have several questions regarding the topic. First off, why is the nodename parameter forbidden when using the AI_PASSIVE flag? What is the intention? It does not make any sense to me. Is there any (preferably POSIX-conform) way to find local addresses that I can bind to and that correspond to a given host name or IP address in textual representation at all? Provided that the given nodename corresponds to a local address and AI_PASSIVE is not set, will there be any addresses returned by getaddrinfo, that can not be used for binding? Or worse, will there be any addresses that are only suitable for binding and that will not be returned in this case?

Related (and not answered satisfactorily): getaddrinfo: in what way is AI_PASSIVE ignored if the nodename is specified?


回答1:


I agree the reference question was not answered satisfactorily and IMO the whole feature is poorly specified.

I believe the AI_PASSIVE flag is misnamed. It should be called something like AI_ANY_ADDRESS. It just means "give me a token I can use to bind to any node address on this system (within the address family, etc)" so that you don't have to hard-code INADDR_ANY and IN6ADDR_ANY_INIT. Hence, if you supply a node name argument, you're obviating the point of AI_PASSIVE. You're saying "Here's the address(es) I want to bind to".

In practice, this all works out fine because the structure of addresses accepted by bind and connect is fundamentally the same in all cases (except that you can't connect to the special "any" address). That's why -- when you specify the node name -- it doesn't matter whether you specify AI_PASSIVE or not. You're going to get the parameters appropriate for a bind or connect call on the specified name/address.

Obtaining that "address info" doesn't mean the bind or connect will succeed. You could successfully obtain an address that you can't bind to -- because it's not an address of the local machine -- or for other reasons as well. (And obviously, the connect could fail for many reasons as well -- there might be no machine answering at that address or no server listening on the port, etc.)

If you supply a node name that is not an IP address and name resolution provides multiple IP addresses in different families (that are all available on the local machine), you should be able to bind to all of those addresses.

So, bottom line: If you want to allow the user to specify the address, just provide that to getaddrinfo. The address(es) you get back (if any) should be usable in a bind call.



来源:https://stackoverflow.com/questions/39026202/how-to-choose-a-server-socket-address-using-getaddrinfo

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!