Opened 3 years ago

Closed 3 years ago

#1447 closed defect (wontfix)

Specific IP(v4) listen directives cause "address already in use" when using ipv6only=off

Reported by: indecisivemal@… Owned by:
Priority: minor Milestone:
Component: other Version: 1.13.x
Keywords: ipv6only, ngx_http Cc:
uname -a: Linux mydebian 4.9.0-4-grsec-amd64 #1 SMP Debian 4.9.51-1+grsecunoff2~bpo9+1 (2017-10-11) x86_64 GNU/Linux
nginx -V: nginx version: nginx/1.13.7
built by gcc 6.3.0 20170516 (Debian 6.3.0-18)
built with OpenSSL 1.1.0f 25 May 2017
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/nginx-1.13.7/debian/debuild-base/nginx-1.13.7=. -specs=/usr/share/dpkg/no-pie-compile.specs -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-specs=/usr/share/dpkg/no-pie-link.specs -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'

Description

Attempting to have one server block listening on all addresses (v4 and v6) and a second server block listening only on one IPv4 address.

When using ipv6only=off to accomplish the first with a single listen statement, the specific IP listen statement in the second causes nginx to report "address already in use" and exit(1):

Dec 15 01:15:35 mydebian systemd[1]: Starting nginx - high performance web server...
Dec 15 01:15:35 mydebian nginx[20599]: nginx: [emerg] bind() to 127.0.0.1:80 failed (98: Address already in use)
[previous message repeated 4 more times]
Dec 15 01:15:37 mydebian nginx[20599]: nginx: [emerg] still could not bind()
Dec 15 01:15:37 mydebian systemd[1]: nginx.service: Control process exited, code=exited status=1

Scattering log calls around, it seems like src/http/ngx_http.c ngx_http_add_listen() needs awareness of ipv6only=off so when it tests new proto=AF_INET :80 against existing proto=AF_INET6 :80 it considers it known instead of new.

Example nginx.conf:

events {}
http {
    server {
        listen [::]:80 ipv6only=off;
    }
    server {
        listen 127.0.0.1:80;
    }
}

Problem observed with:
Arch nginx-mainline 1.13.7 (Linux myarch 4.14.5-1-ARCH #1 SMP PREEMPT Sun Dec 10 14:50:30 UTC 2017 x86_64 GNU/Linux)
Arch with nginx 1.13.7 from hg, default ./configure
Debian with nginx-mainline from nginx repo (shown)

Both default to net.ipv6.bindv6only=0

Change History (1)

comment:1 by Maxim Dounin, 3 years ago

Resolution: wontfix
Status: newclosed

The problem here is Linux-specific "security" check which doesn't allow listening sockets to coexists with more specific ones on the same port, like 0.0.0.0:80 and 127.0.0.1:80. While nginx handles this when you specify listening sockets without additional options, it is trivial to run into problems on Linux if you specify additional parameters of the listen directive. Quoting the description of the bind parameter of the listen directive:

bind

instructs to make a separate bind() call for a given address:port pair. This is useful because if there are several listen directives with the same port but different addresses, and one of the listen directives listens on all addresses for the given port (*:port), nginx will bind() only to *:port. It should be noted that the getsockname() system call will be made in this case to determine the address that accepted the connection. If the setfib, backlog, rcvbuf, sndbuf, accept_filter, deferred, ipv6only, or so_keepalive parameters are used then for a given address:port pair a separate bind() call will always be made.

There are no plans to teach nginx to recognize IPv6-with-mapped-IPv4 listening sockets and merge them with IPv4 sockets on the same port like we do with wildcard and non-wildcard addresses. In particular, because the configuration provided will result in IPv6 connections in the server with listen 127.0.0.1:80, which is at least unexpected (and completely broken in some more complex configrations - for example, when the client address is used in proxy_bind).

Instead, nginx universally switches on the IPV6_V6ONLY socket option by default (see bdcdbdf35b52). To support both IPv6 and IPv4, configure listening sockets for both protocols:

listen 80;
listen [::]:80;

This way nginx will be able to work with listening sockets in a predictable way, identical on a wide range of platforms. And it will be able to properly merge wildcard and non-wildcard listening sockets to avoid problems with the Linux-specific check in question.

If you still want to configure IPv6-with-IPv4 sockets instead for some reasons, you can do so, but it is your responsibility to avoid configurations not allowed by your OS.

Note: See TracTickets for help on using tickets.