Dual stack listeners on Linux

By Brian Fitzgerald

Question

$ netstat -ntl | grep :1521
tcp6 0 0 :::1521 :::* LISTEN

Q: Does the netstat output shown here mean that the listener accepts only IPv6 connections?

A: No. By default, a Linux listener uses a dual stack socket.

Oracle listener trace

Here is an strace of the Oracle TNS listener socket binding.

32649 socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 8
32649 setsockopt(8, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
32649 bind(8, {sa_family=AF_INET6, sin6_port=htons(1521), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
32649 listen(8, 128)

In the bind call, notice that the socket address family is AF_INET6, and the IP address shown is “::”, meaning listen on all IP addresses on the local host. The netstat output looks like this:

[grid@ip-172-32-10-34 ~]$ netstat -ntl | grep :1521
tcp6 0 0 :::1521 :::* LISTEN

However, the listener will accept either IPv4 or IPv6. You can check this by testing IPv4 and IPv6 one at a time:

[ec2-user@ip-172-32-10-34 ~]$ nc -v -4 localhost 1521 < /dev/null
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 127.0.0.1:1521.
Ncat: 0 bytes sent, 0 bytes received in 0.02 seconds.
[ec2-user@ip-172-32-10-34 ~]$ nc -v -6 localhost 1521 < /dev/null
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to ::1:1521.
Ncat: 0 bytes sent, 0 bytes received in 0.03 seconds.

The connect calls were:

connect(3, {sa_family=AF_INET, sin_port=htons(1521), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
connect(3, {sa_family=AF_INET6, sin6_port=htons(1521), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = -1 EINPROGRESS (Operation now in progress)

You can compare the single-stack Oracle listener to other listeners that use separate sockets.

Linux sshd trace

By comparison, here is a trace of the sshd listener socket bindings.

1142 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
1142 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
1142 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
1142 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
1142 bind(3, {sa_family=AF_INET, sin_port=htons(22), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
1142 listen(3, 128) = 0
...
1142 socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP) = 4
1142 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR)
1142 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0
1142 setsockopt(4, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
1142 setsockopt(4, SOL_IPV6, IPV6_V6ONLY, [1], 4) = 0
1142 bind(4, {sa_family=AF_INET6, sin6_port=htons(22), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
1142 listen(4, 128)

Notice that for socket 3, the address family is AF_INET and the listener IP address is given as “0.0.0.0”, again meaning listen on all IP addresses. Examining socket 4 trace carefully, we see that before the bind call, socket option IPV6_V6ONLY is set. The netstat output looks like this:

[ec2-user@ip-172-32-10-34 ~]$ netstat -ntl | grep :22
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp6 0 0 :::22 :::* LISTEN

netstat displays one output line per socket. sshd has two listener sockets, one for IPv4 and a separate IPv6 socket.

IPv6-only listener

You can demonstrate an IPv6-only listener:

[ec2-user@ip-172-32-10-34 ~]$ nc -6 -l 6666
[ec2-user@ip-172-32-10-34 ~]$ netstat -ntl | grep :6666
tcp6       0      0 :::6666                 :::*                    LISTEN

An IPv4 connection fails:

[ec2-user@ip-172-32-10-34 ~]$ nc -4 localhost 6666
Ncat: Connection refused.

The nc utility makes two connection attempts:

socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
connect(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
...
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 4
fcntl(4, F_GETFL)                       = 0x2 (flags O_RDWR)
fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
connect(4, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)

IPv4-only listener

Likewise, you can demonstrate an IPv4-only listener.

[ec2-user@ip-172-32-10-34 ~]$ nc -4 -l 4444

Netstat:

[ec2-user@ip-172-32-10-34 ~]$ netstat -ntl | grep :4444
tcp 0 0 0.0.0.0:4444 0.0.0.0:* LISTEN

IPv6 connection fails:

[ec2-user@ip-172-32-10-34 ~]$ nc -6 localhost 4444
Ncat: Connection refused.

The connect call:

connect(3, {sa_family=AF_INET6, sin6_port=htons(4444), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = -1 EINPROGRESS (Operation now in progress)

Conclusion

In the netstat output,

[ec2-user@ip-172-32-10-34 ~]$ netstat -ntl | grep :1521
tcp6 0 0 :::1521 :::* LISTEN

The lack of a line such as

tcp 0 0 0.0.0.0:1521 0.0.0.0:* LISTEN

does not mean that the listener does not accept IPv4 connections. It could mean that the listener implements a dual stack socket.

Leave a Reply