IPv6 socket listening in6addr_any problem

When we check the listening port of this machine with netstat -lnt , we often see a display similar to the following:

tcp6       0      0 :::22                   :::*                    LISTEN      658/sshd: /usr/sbin

Obviously, sshd created an IPv6 socket and listened on port 22 on the in6addr_any address.

At this time, I use an IPv4 address of the machine to connect to port 22, but does it work or not? In order to avoid irrelevant discussions, I assume that the value of net.ipv6.bindv6only is 0.

Of course it works. If you don't believe me, try it. If you want to know the details, why look at the source code, this code is very simple. I would like to raise a question related to reuseport.

According to the semantics of TCP, listening to a port has nothing to do with the IP address, only the port. According to the semantics of the socket, bind an address needs to provide both an IP address and a port.

Therefore, in terms of implementation, we have to distinguish which is specified by TCP and which is specified by socket:

  • Socket must be classified according to the address family, such as IPv4 socket and IPv6 socket.
  • TCP listening on a port cannot distinguish whether the connection comes from an IPv4 address or an IPv6 address.

IPv4 socket is the AF_INET family, and IPv6 socket is the AF_INET6 family. I hate the terminology, so I won’t say this.

In terms of implementation, Linux obviously uses the same hash table to store all listening sockets including IPv4 and IPv6. Whether it is an IPv4 or IPv6 listening socket, at the end of the bind, it will be inserted with its bind port as the key value. To the same hash table, note that this hash table has nothing to do with the IP address.

When the TCP connection comes, the protocol stack will extract the destination port of the data packet, and use this as the key value to query the only hash table that stores the listening socket. We assume that a matching IPv6 socket is found, and the socket binds Is the in6addr_any address, then the problem is here:

  • If the source connection is an IPv6 packet, it is obvious that the connection can be successfully established.
  • If the source connection is an IPv4 packet, can it establish a connection?

It depends on how to understand the in6addr_any address , that is, "0:0:0:0:0:0:0:0" This IPv6 address includes "0.0.0.0" which does not include IPv4 . For Linux systems, it is closed in bindv6only In this case, the answer is obviously yes. Therefore, when an IPv6 socket listens after bind in6addr_any, the connection can be successfully established regardless of whether it is using IPv4 or IPv6.

For example, I bind an IPv6 address with the following code:

inet_pton(AF_INET6, "0:0:0:0:0:0:0:0", (struct sockaddr_in6 *)&srvaddr.sin6_addr);
srvaddr.sin6_port = htons(1234);
bind(lsd, (struct sockaddr*)&srvaddr, sizeof(srvaddr));
listen(lsd, 10);

Then I use an IPv4 address to connect:

telnet 192.168.56.101 1234

After the listener accepts the connection request, it will resolve the source address to the IPv4-Mapped address of the source IPv4 address "::ffff:192.168.56.102". If you use netstat to check the connection, the display is still an IPv4 connection:

tcp6       0      0 192.168.56.101:1234     192.168.56.102:52802    ESTABLISHED 29047/./a.out

Now the details are very clear. The question is, how to make IPv6 sockets no longer accept IPv4 connections while keeping bindv6only as 0?

It's not difficult, the method is:

  • Enable reuseport for IPv6 socket, then create an IPv4 socket and bind to the same port of 0.0.0.0.

In this way, even if the IPv6 socket is listening on in6addr_any, it will not accept IPv4 connections, and IPv4 connections are completely handled by IPv4 sockets.

This is very interesting in the implementation of Linux because it is so simple. Simply put, for the case of listening on the same port:

  • The IPv6 socket is inserted at the end of the hash list.
  • The IPv4 socket is inserted into the head of the hash list.
  • The lookup of the listening socket starts from the head of the hash list.

Obviously, IPv4 socket and IPv6 socket listening on the same port cannot be in the same reuseport group, they can only be traversed according to their position in the linked list. Therefore, if an IPv4 connection request comes, it will hit the IPv4 socket first before traversing to the IPv6 socket.

The simple logic need not be said too much.

OpenBSD is different from Linux in that it does not allow IPv4 packets to be processed by an IPv6 socket. Even if the IPv6 socket has already bound the in6addr_any address, it must be handled separately. Compared with Linux, the implementation of OpenBSD is simpler or more complicated?

unknown.

I wanted to talk about IPv4-Mapped addresses again, especially security-related issues, but time is not allowed. For details, see: https://lwn.net/Articles/8646/


The leather shoes in Wenzhou, Zhejiang are wet, so they won’t get fat in the rain.

Guess you like

Origin blog.csdn.net/dog250/article/details/112058148