dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.22k stars 1.57k forks source link

RawDatagramSocket.bind() reusePort and reuseAddress parameters have overlapping behaviour #49600

Open sgrekhov opened 2 years ago

sgrekhov commented 2 years ago

Please see RawDatagramSocket.bind() documentation

Future<RawDatagramSocket> bind(
dynamic host,
int port,
{bool reuseAddress = true,
bool reusePort = false,
int ttl = 1}
)

... The reuseAddress should be set for all listeners that bind to the same address. Otherwise, it will fail with a SocketException. The reusePort specifies whether the port can be reused.

But in fact, the actual the behaviour is quite sophisticated. Consider the following test

import "dart:io";

var localhost = InternetAddress.loopbackIPv4;

main() async {
  final socket1 = await RawDatagramSocket.bind(localhost, 0, reuseAddress: true, reusePort: true);
  final socket2 = await RawDatagramSocket.bind(localhost, socket1.port, reuseAddress: false, reusePort: false);
}

Now let's play with socket1 and socket2 parameters. Results are the following

  socket1: reuseAddress: false, reusePort: false socket1: reuseAddress: false, reusePort: true socket1: reuseAddress: true, reusePort: false socket1: reuseAddress: true, reusePort: true
socket2:reuseAddress: false, reusePort: false SocketException SocketException SocketException SocketException
socket2:reuseAddress: false, reusePort: true SocketException Ok SocketException Ok
socket2:reuseAddress: true, reusePort: false SocketException SocketException Ok Ok
socket2:reuseAddress: true, reusePort: true SocketException Ok Ok Ok

Analyzing the table above we came to conclusion that sockets may share the same address if they both have either reuseAddress: true either reusePort: true. Is this expected? If so, then it should be clearly reflected in the documentation. Now it is not clear what is the difference between these two parameters

Also, having two parameters named reusePort and reuseAddress is a bit of misleading because port is the part of the address

Tested on the edge version of the SDK Dart SDK version: 2.19.0-edge.5b489e6505de3dc6fde98c136862ac4a89724fea (be) (Fri Aug 5 08:10:01 2022 +0000) on "linux_x64"

a-siva commented 2 years ago

I believe it is working as expected based on this documentation of the options in Linux socket API

SO_REUSEPORT (since Linux 3.9)
              Permits multiple AF_INET or AF_INET6 sockets to be bound
              to an identical socket address.  This option must be set
              on each socket (including the first socket) prior to
              calling [bind(2)](https://man7.org/linux/man-pages/man2/bind.2.html) on the socket.  To prevent port hijacking,
              all of the processes binding to the same address must have
              the same effective UID.  This option can be employed with
              both TCP and UDP sockets.

              For TCP sockets, this option allows [accept(2)](https://man7.org/linux/man-pages/man2/accept.2.html) load
              distribution in a multi-threaded server to be improved by
              using a distinct listener socket for each thread.  This
              provides improved load distribution as compared to
              traditional techniques such using a single [accept(2)](https://man7.org/linux/man-pages/man2/accept.2.html)ing
              thread that distributes connections, or having multiple
              threads that compete to [accept(2)](https://man7.org/linux/man-pages/man2/accept.2.html) from the same socket.

              For UDP sockets, the use of this option can provide better
              distribution of incoming datagrams to multiple processes
              (or threads) as compared to the traditional technique of
              having multiple processes compete to receive datagrams on
              the same socket.

Per this when SO_REUSEPORT is set on both socket1 and socket2, bind should succeed.

SO_REUSEADDR
              Indicates that the rules used in validating addresses
              supplied in a [bind(2)](https://man7.org/linux/man-pages/man2/bind.2.html) call should allow reuse of local
              addresses.  For AF_INET sockets this means that a socket
              may bind, except when there is an active listening socket
              bound to the address.  When the listening socket is bound
              to INADDR_ANY with a specific port then it is not possible
              to bind to this port for any local address.  Argument is
              an integer boolean flag.

Per this a socket not needing to call “listen” system call is allowed to bind to the exact same ip address and same port of another socket with same option (TCP Client, UDP Server , UDP Client)

sgrekhov commented 2 years ago

If this is an expected, then please, add this info to the bind() method documentation. Consider

  final socket1 =  await RawDatagramSocket.bind(localhost, 0, reusePort: false);
  final socket2 = await RawDatagramSocket.bind(localhost, socket1.port, reusePort: false);

I'd expect the above produces a SocketException because reusePort: false but, in fact, there is no error here because both socket1 and socket2 have optional parameter reuseAddress with the default value true. It's not obvious to figure out why there is no exception here with the current documentation. Also, if these parameters are not supported under Windows (see https://github.com/dart-lang/sdk/issues/49598) then add it to the documentation

a-siva commented 2 years ago

Will update the docs, thanks.

sgrekhov commented 2 years ago

@a-siva similar to HttpServer.bind() please also add

If port has the value 0 an ephemeral port will be chosen by the system. The actual port used can be retrieved using the port getter.

sgrekhov commented 2 years ago

For Mac and dart legacy see https://github.com/dart-lang/sdk/issues/50172