Opened 5 years ago

Closed 5 years ago

#1677 closed defect (wontfix)

nginx does not resolve hostnames if there is no route

Reported by: wanneut@… Owned by:
Priority: minor Milestone:
Component: nginx-core Version: 1.15.x
Keywords: dns Cc:
uname -a: Linux 3.10.0-862.14.4.el7.x86_64 #1 SMP Wed Sep 26 15:12:11 UTC 2018 x86_64 GNU/Linux
nginx -V: nginx version: nginx/1.12.2

Description

In my configuration is the following line:
proxy_pass http://localhost4:7500;
Problem is, that I am using it on a host without IPv4 connectivity.
This shouldn't b a problem since 127/8 can be accessed without a route over the lo interface.
But it is. nginx fails with the following message:
nginx: [emerg] host not found in upstream "localhost4" in
This should not be happening, since localhost4 can be resolved over /etc/hosts. So gethostbyname should work properly.
There is a working DNS resolution over IPv6.
A workaround would be something like that:

ip addr add 192.0.2.1/24 dev eth0
systemctl restart nginx
ip addr del 192.0.2.1/24 dev eth0

Related:
https://trac.nginx.org/nginx/ticket/1040
https://trac.nginx.org/nginx/ticket/696
https://trac.nginx.org/nginx/ticket/1417

Change History (5)

comment:1 by Maxim Dounin, 5 years ago

Resolution: wontfix
Status: newclosed

It looks like due to no IPv4 connectivity getaddrinfo() on your OS filters out IPv4 addresses as nginx use AI_ADDRCONFIG flag (see ec8594b9bf11).

I don't think we can do anything with this. Removing AI_ADDRCONFIG is not an option as it is likely to break configurations without IPv6 configured.

If you want to use IPv4 loopback address for some reason on a system without IPv4 connectivity, consider using IPv4 address directly, e.g.:

proxy_pass http://127.0.0.1:7500;

Alternatively, consider tuning your system resolver to return such addresses even with IPv4 connectivity disabled. Not sure if it's possible though.

comment:2 by wanneut@…, 5 years ago

Resolution: wontfix
Status: closedreopened
it is likely to break configurations without IPv6 configured.

This is wrong. getaddrinfo dosen't return IPv6-addresses if they aren't reachable or even only reachable over tunneling. (At least on Linux and Windows.)
The comment to #ec8594b9bf11 is misleading:

On Linux, setting net.ipv6.conf.all.disable_ipv6 to 1 will now be
respected.

This is right. But it was respected even before the patch.
He only difference is, when resolving a IPv6 only address on a IPv4 only system. Then NULL returns a IPv6-address while AI_ADDRCONFIG returns nothing. This is not the case on dualstack addresses. If there is at least one A-record no additional IPv6 addresses will be returned as long there is no route to them.
But in this case connect will fail anyway. IPv6 only address can never be accessed in a IPv4-only network. So there is no system that will work with AI_ADDRCONFIG but not without.
The only case when AI_ADDRCONFIG can be useful, is when you bind. (And even there I would highly prefer manage it in later.) For connect it's never a good idea.
It just breaks some systems while it will never do something useful.

comment:3 by Maxim Dounin, 5 years ago

Resolution: wontfix
Status: reopenedclosed

While on some systems getaddrinfo() indeed does not return IPv6 addresses regardless of AI_ADDRCONFIG (e.g., macOS seems to be doing this), at least Linux and FreeBSD without IPv6 on the host return all addresses when ai_flags is 0, and only IPv4 addresses with ai_flags = AI_ADDRCONFIG.

For example:

$ ./getaddrinfo 
206.251.255.63
95.211.80.227
2001:1af8:4060:a004:21::e3
2606:7100:1:69::3f
$ ./getaddrinfo addrconfig
206.251.255.63
95.211.80.227

Here is a simple test code you can play with to find it out yourself:

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


int
main(int argc, char *argv[])
{
    char                  buf[INET6_ADDRSTRLEN];
    void                 *addr;
    struct addrinfo       hints, *res;
    struct sockaddr_in   *in;
    struct sockaddr_in6  *in6;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if (argc > 1) {
        if (strcmp(argv[1], "addrconfig") != 0) {
            fprintf(stderr, "unknown argument\n");
            exit(1);
        }

        hints.ai_flags = AI_ADDRCONFIG;
    }

    getaddrinfo("nginx.org", NULL, &hints, &res);

    for ( /* void */ ; res != NULL; res = res->ai_next) {

        if (res->ai_family == AF_INET) {
            in = (struct sockaddr_in *) res->ai_addr;
            addr = &in->sin_addr;

        } else if (res->ai_family == AF_INET6) {
            in6 = (struct sockaddr_in6 *) res->ai_addr;
            addr = &in6->sin6_addr;

        } else {
            fprintf(stderr, "unknown address family: %d\n", res->ai_family);
            continue;
        }
        
        fprintf(stdout, "%s\n",
                inet_ntop(res->ai_family, addr, buf, sizeof(buf)));
    }

    return 0;
}

comment:4 by wanneut@…, 5 years ago

Resolution: wontfix
Status: closedreopened

The for-loop is wrong if you want to use connect.
How do you connect to more than one address?
You can try everyone. Then everything works, since you would try every protocol.
Or you could use the first one. Which is on your system 206.251.255.63 (since it has no iPv6) if you use your program on a dualstack or IPv6-only system it would be 2001:1af8:4060:a004:21::e3
Which would work again.
Still: There is no system that works with AI_ADDRCONFIG but not without but a lot of systems that work without AI_ADDRCONFIG but not with AI_ADDRCONFIG.

comment:5 by Maxim Dounin, 5 years ago

Resolution: wontfix
Status: reopenedclosed

When you write a name in the proxy_pass directive, nginx uses all addresses, quoting docs:

If a domain name resolves to several addresses, all of them will be used in a round-robin fashion.

The approach of using the first result of getaddrinfo() without AI_ADDRCONFIG may work for some naive clients which use simple connect(), as the first address is expected to be the best available one per RFC 3484. This approach won't work for nginx though, as it uses all addresses available.

Note: See TracTickets for help on using tickets.