Skip to content

TCP/UDP socket type selection inconsistency #33331

@nebkat

Description

@nebkat

Is your feature request related to a problem? Please describe.
dgram.Socket requires the socket type (udp4 or udp6) to be provided in its constructor (dgram.createSocket), and uses this socket type regardless of the local and remote addresses.

net.Socket does not require the socket type to be provided in advance, and performs dns.lookup() with the family parameter given in net.Socket#connect() to determine the socket type. Unlike dgram.Socket, it performs socket binding when a connection is requested, so the local address/port can also be provided as parameters if needed, but the socket type is still determined by the remote host (which makes sense as net.Server deals with bound but unconnected sockets).

net.Server uses an IPv6 socket by default, unless an IPv4 address is explicitly provided in net.Server#listen().

This results in inconsistent default behavior when comparing TCP and UDP, both for servers and clients. It should be possible to have the UDP socket type be determined by the provided addresses, both for servers and clients.

Even if udp6 is chosen, which would in theory work the same for both types of remote addresses, the DNS resolution is inconsistent for TCP/UDP clients. TCP resolution defaults to family type 0, while UDP would use family type 6, so the resulting IP address will be different if the nameserver normally resolves to IPv4 first.

See lib/internal/dgram.js#newHandle, lib/net.js#internalConnect and lib/net.js#createServerHandle.

Describe the solution you'd like

  • dgram.Socket should not require a socket type in its constructor.
  • dgram.Socket#bind() should match the net.Server#listen() behavior and use an IPv6 socket by default, unless and IPv4 address is explicitly provided.
  • dgram.Socket#connect() should match the net.Socket#connect() behavior and resolve the remote address to determine the socket type, crucially before the bind() call is made.
  • If the socket is already bound when connect() is called, the functionality should remain as it is currently, where the dns.lookup() call is made with the family parameter set. If only IPv6 is resolved on an IPv4 bound socket, an error is thrown.
  • The socket type currently provided in the constructor could set an internal default address for cases when no local or remote addresses are provided to bind() and connect(), until this method is deprecated, after which the default address would become ::1 in both cases.

Describe alternatives you've considered

  • dns.lookup() can be performed manually before the socket is created to decide udp4/udp6, but this greatly complicates the socket creation flow. This could be moved to dgram.createConnection or similar, but this wouldn't fully solve the TCP/UDP client inconsistency.
  • If using IPv6 by default is acceptable, the lookup parameter of dgram.createSocket can be set to a wrapper around the default dns.lookup(), with the family type set to 0 instead of 6 and if the result is IPv4 it can be manually mapped to IPv6. This again complicates the socket creation flow.
  • The previous alternative could be simplified if family and hints could be provided as is possible in net.Socket#connect(), but this doesn't solve the core problem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    dgramIssues and PRs related to the dgram subsystem / UDP.feature requestIssues that request new features to be added to Node.js.stale

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions