@@ -519,8 +519,12 @@ Start a server listening for connections on a given `handle` that has
519519already been bound to a port, a Unix domain socket, or a Windows named pipe.
520520
521521The ` handle ` object can be either a server, a socket (anything with an
522- underlying ` _handle ` member), or an object with an ` fd ` member that is a
523- valid file descriptor.
522+ underlying ` _handle ` member), a [ ` BoundSocket ` ] [ ] , or an object with an ` fd `
523+ member that is a valid file descriptor.
524+
525+ When ` handle ` is a [ ` BoundSocket ` ] [ ] , the server adopts the already-bound
526+ socket and starts listening on it. Adoption consumes the bound socket (see
527+ [ ownership transfer] [ `BoundSocket` ] ).
524528
525529Listening on a file descriptor is not supported on Windows.
526530
@@ -546,6 +550,10 @@ changes:
546550 * ` backlog ` {number} Common parameter of [ ` server.listen() ` ] [ ]
547551 functions.
548552 * ` exclusive ` {boolean} ** Default:** ` false `
553+ * ` handle ` {net.BoundSocket} A pre-bound [ ` BoundSocket ` ] [ ] . The server adopts
554+ the already-bound socket and listens on it, ignoring ` host ` , ` port ` , and
555+ ` path ` . Adoption consumes the bound socket (see
556+ [ ownership transfer] [ `BoundSocket` ] ).
549557 * ` host ` {string}
550558 * ` ipv6Only ` {boolean} For TCP servers, setting ` ipv6Only ` to ` true ` will
551559 disable dual-stack support, i.e., binding to host ` :: ` won't make
@@ -569,7 +577,8 @@ changes:
569577 functions.
570578* Returns: {net.Server}
571579
572- If ` port ` is specified, it behaves the same as
580+ If ` handle ` is specified, the server adopts that pre-bound socket. Otherwise, if
581+ ` port ` is specified, it behaves the same as
573582[ ` server.listen([port[, host[, backlog]]][, callback]) ` ] [ `server.listen(port)` ] .
574583Otherwise, if ` path ` is specified, it behaves the same as
575584[ ` server.listen(path[, backlog][, callback]) ` ] [ `server.listen(path)` ] .
@@ -763,6 +772,12 @@ changes:
763772 access to specific IP addresses, IP ranges, or IP subnets.
764773 * ` fd ` {number} If specified, wrap around an existing socket with
765774 the given file descriptor, otherwise a new socket will be created.
775+ * ` handle ` {net.BoundSocket} If specified, wrap around the bound socket from a
776+ [ ` BoundSocket ` ] [ ] . A subsequent
777+ [ ` socket.connect() ` ] [ `socket.connect()` ] uses the bound socket as the
778+ connection's source binding (honoring the bound local address and port).
779+ Adoption consumes the bound socket (see
780+ [ ownership transfer] [ `BoundSocket` ] ).
766781 * ` keepAlive ` {boolean} If set to ` true ` , it enables keep-alive functionality on
767782 the socket immediately after the connection is established, similarly on what
768783 is done in [ ` socket.setKeepAlive() ` ] [ ] . ** Default:** ` false ` .
@@ -1617,6 +1632,94 @@ This property represents the state of the connection as a string.
16171632* If the stream is readable and not writable, it is ` readOnly ` .
16181633* If the stream is not readable and writable, it is ` writeOnly ` .
16191634
1635+ ## Class: ` net.BoundSocket `
1636+
1637+ <!-- YAML
1638+ added: REPLACEME
1639+ -->
1640+
1641+ Allows for the synchronous creation of a pre-bound socket, that can be passed
1642+ to ` listen() ` or ` new net.Socket() ` later on. For ` listen() ` this enables
1643+ synchronous port reservation, while for ` new net.Socket() ` , it allows control
1644+ over the local egress port/IP, via ` bind(2) ` semantics.
1645+
1646+ Adoption transfers ownership of the socket; afterwards ` address() ` and ` close() `
1647+ throw [ ` ERR_SOCKET_HANDLE_ADOPTED ` ] [ ] . A handle that is never adopted must be
1648+ closed to avoid leaking the socket.
1649+
1650+ ``` mjs
1651+ import net from ' node:net' ;
1652+
1653+ const bound = new net.BoundSocket ();
1654+ const { port } = bound .address ();
1655+ console .log (` Reserved port ${ port} for server` );
1656+
1657+ const server = net .createServer ();
1658+ server .listen (bound); // Adopt as a server, or pass to new net.Socket() instead.
1659+ ```
1660+
1661+ ### ` new net.BoundSocket([options]) `
1662+
1663+ <!-- YAML
1664+ added: REPLACEME
1665+ -->
1666+
1667+ * ` options ` {Object}
1668+ * ` host ` {string} Local address to bind. Must be a numeric IP literal; no DNS
1669+ resolution is performed. ** Default:** ` '0.0.0.0' ` , or ` '::' ` when
1670+ ` ipv6Only ` is ` true ` .
1671+ * ` port ` {number} Local port. ` 0 ` requests an OS-assigned ephemeral port.
1672+ ** Default:** ` 0 ` .
1673+ * ` ipv6Only ` {boolean} Sets ` IPV6_V6ONLY ` , disabling dual-stack support so the
1674+ socket binds IPv6 only. Only meaningful for IPv6 binds. ** Default:**
1675+ ` false ` .
1676+ * ` reusePort ` {boolean} Sets ` SO_REUSEPORT ` , allowing multiple sockets to bind
1677+ the same address and port for kernel-level load balancing. Support is
1678+ platform-dependent. ** Default:** ` false ` .
1679+
1680+ ### ` boundSocket.address() `
1681+
1682+ <!-- YAML
1683+ added: REPLACEME
1684+ -->
1685+
1686+ * Returns: {Object} An object with ` address ` , ` family ` , and ` port ` properties,
1687+ as [ ` server.address() ` ] [ ] returns.
1688+
1689+ Returns the bound local address. When bound with ` port: 0 ` , ` port ` is the
1690+ OS-assigned ephemeral port.
1691+
1692+ ### ` boundSocket.fd() `
1693+
1694+ <!-- YAML
1695+ added: REPLACEME
1696+ -->
1697+
1698+ * Returns: {integer} The underlying OS file descriptor, or ` -1 ` on platforms
1699+ that do not expose one for sockets (such as Windows).
1700+
1701+ Returns the file descriptor of the bound socket. Ownership remains with the
1702+ ` BoundSocket ` , so the descriptor must not be closed by the caller. The
1703+ descriptor is only available before the handle is adopted; afterwards it belongs
1704+ to the adopting [ ` net.Server ` ] [ ] or [ ` net.Socket ` ] [ ] and ` fd() ` throws
1705+ [ ` ERR_SOCKET_HANDLE_ADOPTED ` ] [ ] .
1706+
1707+ ### ` boundSocket.close() `
1708+
1709+ <!-- YAML
1710+ added: REPLACEME
1711+ -->
1712+
1713+ Releases the bound socket. Only needed when the handle is never adopted.
1714+
1715+ ### ` boundSocket[Symbol.dispose]() `
1716+
1717+ <!-- YAML
1718+ added: REPLACEME
1719+ -->
1720+
1721+ Closes the handle if it has not been adopted or closed; otherwise a no-op.
1722+
16201723## ` net.connect() `
16211724
16221725Aliases to
@@ -1711,6 +1814,9 @@ and [`socket.connect(options[, connectListener])`][`socket.connect(options)`].
17111814
17121815Additional options:
17131816
1817+ * ` handle ` {net.BoundSocket} A pre-bound [ ` BoundSocket ` ] [ ] used as the
1818+ connection's source binding, honoring its local address and port. Adoption
1819+ consumes the bound socket (see [ ownership transfer] [ `BoundSocket` ] ).
17141820* ` timeout ` {number} If set, will be used to call
17151821 [ ` socket.setTimeout(timeout) ` ] [ ] after the socket is created, but before
17161822 it starts the connection.
@@ -2087,6 +2193,8 @@ net.isIPv6('fhqwhgads'); // returns false
20872193[ `'error'` ] : #event-error_1
20882194[ `'listening'` ] : #event-listening
20892195[ `'timeout'` ] : #event-timeout
2196+ [ `BoundSocket` ] : #class-netboundsocket
2197+ [ `ERR_SOCKET_HANDLE_ADOPTED` ] : errors.md#err_socket_handle_adopted
20902198[ `EventEmitter` ] : events.md#class-eventemitter
20912199[ `child_process.fork()` ] : child_process.md#child_processforkmodulepath-args-options
20922200[ `dns.lookup()` ] : dns.md#dnslookuphostname-options-callback
@@ -2106,6 +2214,7 @@ net.isIPv6('fhqwhgads'); // returns false
21062214[ `net.getDefaultAutoSelectFamilyAttemptTimeout()` ] : #netgetdefaultautoselectfamilyattempttimeout
21072215[ `new net.Socket(options)` ] : #new-netsocketoptions
21082216[ `readable.setEncoding()` ] : stream.md#readablesetencodingencoding
2217+ [ `server.address()` ] : #serveraddress
21092218[ `server.close()` ] : #serverclosecallback
21102219[ `server.dropMaxConnection` ] : #serverdropmaxconnection
21112220[ `server.listen()` ] : #serverlisten
0 commit comments