@@ -523,8 +523,12 @@ Start a server listening for connections on a given `handle` that has
523523already been bound to a port, a Unix domain socket, or a Windows named pipe.
524524
525525The ` handle ` object can be either a server, a socket (anything with an
526- underlying ` _handle ` member), or an object with an ` fd ` member that is a
527- valid file descriptor.
526+ underlying ` _handle ` member), a [ ` BoundSocket ` ] [ ] , or an object with an ` fd `
527+ member that is a valid file descriptor.
528+
529+ When ` handle ` is a [ ` BoundSocket ` ] [ ] , the server adopts the already-bound
530+ socket and starts listening on it. Adoption consumes the bound socket (see
531+ [ ownership transfer] [ `BoundSocket` ] ).
528532
529533Listening on a file descriptor is not supported on Windows.
530534
@@ -550,6 +554,10 @@ changes:
550554 * ` backlog ` {number} Common parameter of [ ` server.listen() ` ] [ ]
551555 functions.
552556 * ` exclusive ` {boolean} ** Default:** ` false `
557+ * ` handle ` {net.BoundSocket} A pre-bound [ ` BoundSocket ` ] [ ] . The server adopts
558+ the already-bound socket and listens on it, ignoring ` host ` , ` port ` , and
559+ ` path ` . Adoption consumes the bound socket (see
560+ [ ownership transfer] [ `BoundSocket` ] ).
553561 * ` host ` {string}
554562 * ` ipv6Only ` {boolean} For TCP servers, setting ` ipv6Only ` to ` true ` will
555563 disable dual-stack support, i.e., binding to host ` :: ` won't make
@@ -573,7 +581,8 @@ changes:
573581 functions.
574582* Returns: {net.Server}
575583
576- If ` port ` is specified, it behaves the same as
584+ If ` handle ` is specified, the server adopts that pre-bound socket. Otherwise, if
585+ ` port ` is specified, it behaves the same as
577586[ ` server.listen([port[, host[, backlog]]][, callback]) ` ] [ `server.listen(port)` ] .
578587Otherwise, if ` path ` is specified, it behaves the same as
579588[ ` server.listen(path[, backlog][, callback]) ` ] [ `server.listen(path)` ] .
@@ -769,6 +778,12 @@ changes:
769778 access to specific IP addresses, IP ranges, or IP subnets.
770779 * ` fd ` {number} If specified, wrap around an existing socket with
771780 the given file descriptor, otherwise a new socket will be created.
781+ * ` handle ` {net.BoundSocket} If specified, wrap around the bound socket from a
782+ [ ` BoundSocket ` ] [ ] . A subsequent
783+ [ ` socket.connect() ` ] [ `socket.connect()` ] uses the bound socket as the
784+ connection's source binding (honoring the bound local address and port).
785+ Adoption consumes the bound socket (see
786+ [ ownership transfer] [ `BoundSocket` ] ).
772787 * ` keepAlive ` {boolean} If set to ` true ` , it enables keep-alive functionality on
773788 the socket immediately after the connection is established, similarly on what
774789 is done in [ ` socket.setKeepAlive() ` ] [ ] . ** Default:** ` false ` .
@@ -1627,6 +1642,94 @@ This property represents the state of the connection as a string.
16271642* If the stream is readable and not writable, it is ` readOnly ` .
16281643* If the stream is not readable and writable, it is ` writeOnly ` .
16291644
1645+ ## Class: ` net.BoundSocket `
1646+
1647+ <!-- YAML
1648+ added: REPLACEME
1649+ -->
1650+
1651+ Allows for the synchronous creation of a pre-bound socket, that can be passed
1652+ to ` listen() ` or ` new net.Socket() ` later on. For ` listen() ` this enables
1653+ synchronous port reservation, while for ` new net.Socket() ` , it allows control
1654+ over the local egress port/IP, via ` bind(2) ` semantics.
1655+
1656+ Adoption transfers ownership of the socket; afterwards ` address() ` and ` close() `
1657+ throw [ ` ERR_SOCKET_HANDLE_ADOPTED ` ] [ ] . A handle that is never adopted must be
1658+ closed to avoid leaking the socket.
1659+
1660+ ``` mjs
1661+ import net from ' node:net' ;
1662+
1663+ const bound = new net.BoundSocket ();
1664+ const { port } = bound .address ();
1665+ console .log (` Reserved port ${ port} for server` );
1666+
1667+ const server = net .createServer ();
1668+ server .listen (bound); // Adopt as a server, or pass to new net.Socket() instead.
1669+ ```
1670+
1671+ ### ` new net.BoundSocket([options]) `
1672+
1673+ <!-- YAML
1674+ added: REPLACEME
1675+ -->
1676+
1677+ * ` options ` {Object}
1678+ * ` host ` {string} Local address to bind. Must be a numeric IP literal; no DNS
1679+ resolution is performed. ** Default:** ` '0.0.0.0' ` , or ` '::' ` when
1680+ ` ipv6Only ` is ` true ` .
1681+ * ` port ` {number} Local port. ` 0 ` requests an OS-assigned ephemeral port.
1682+ ** Default:** ` 0 ` .
1683+ * ` ipv6Only ` {boolean} Sets ` IPV6_V6ONLY ` , disabling dual-stack support so the
1684+ socket binds IPv6 only. Only meaningful for IPv6 binds. ** Default:**
1685+ ` false ` .
1686+ * ` reusePort ` {boolean} Sets ` SO_REUSEPORT ` , allowing multiple sockets to bind
1687+ the same address and port for kernel-level load balancing. Support is
1688+ platform-dependent. ** Default:** ` false ` .
1689+
1690+ ### ` boundSocket.address() `
1691+
1692+ <!-- YAML
1693+ added: REPLACEME
1694+ -->
1695+
1696+ * Returns: {Object} An object with ` address ` , ` family ` , and ` port ` properties,
1697+ as [ ` server.address() ` ] [ ] returns.
1698+
1699+ Returns the bound local address. When bound with ` port: 0 ` , ` port ` is the
1700+ OS-assigned ephemeral port.
1701+
1702+ ### ` boundSocket.fd() `
1703+
1704+ <!-- YAML
1705+ added: REPLACEME
1706+ -->
1707+
1708+ * Returns: {integer} The underlying OS file descriptor, or ` -1 ` on platforms
1709+ that do not expose one for sockets (such as Windows).
1710+
1711+ Returns the file descriptor of the bound socket. Ownership remains with the
1712+ ` BoundSocket ` , so the descriptor must not be closed by the caller. The
1713+ descriptor is only available before the handle is adopted; afterwards it belongs
1714+ to the adopting [ ` net.Server ` ] [ ] or [ ` net.Socket ` ] [ ] and ` fd() ` throws
1715+ [ ` ERR_SOCKET_HANDLE_ADOPTED ` ] [ ] .
1716+
1717+ ### ` boundSocket.close() `
1718+
1719+ <!-- YAML
1720+ added: REPLACEME
1721+ -->
1722+
1723+ Releases the bound socket. Only needed when the handle is never adopted.
1724+
1725+ ### ` boundSocket[Symbol.dispose]() `
1726+
1727+ <!-- YAML
1728+ added: REPLACEME
1729+ -->
1730+
1731+ Closes the handle if it has not been adopted or closed; otherwise a no-op.
1732+
16301733## ` net.connect() `
16311734
16321735Aliases to
@@ -1721,6 +1824,9 @@ and [`socket.connect(options[, connectListener])`][`socket.connect(options)`].
17211824
17221825Additional options:
17231826
1827+ * ` handle ` {net.BoundSocket} A pre-bound [ ` BoundSocket ` ] [ ] used as the
1828+ connection's source binding, honoring its local address and port. Adoption
1829+ consumes the bound socket (see [ ownership transfer] [ `BoundSocket` ] ).
17241830* ` timeout ` {number} If set, will be used to call
17251831 [ ` socket.setTimeout(timeout) ` ] [ ] after the socket is created, but before
17261832 it starts the connection.
@@ -2097,6 +2203,8 @@ net.isIPv6('fhqwhgads'); // returns false
20972203[ `'error'` ] : #event-error_1
20982204[ `'listening'` ] : #event-listening
20992205[ `'timeout'` ] : #event-timeout
2206+ [ `BoundSocket` ] : #class-netboundsocket
2207+ [ `ERR_SOCKET_HANDLE_ADOPTED` ] : errors.md#err_socket_handle_adopted
21002208[ `EventEmitter` ] : events.md#class-eventemitter
21012209[ `child_process.fork()` ] : child_process.md#child_processforkmodulepath-args-options
21022210[ `dns.lookup()` ] : dns.md#dnslookuphostname-options-callback
@@ -2116,6 +2224,7 @@ net.isIPv6('fhqwhgads'); // returns false
21162224[ `net.getDefaultAutoSelectFamilyAttemptTimeout()` ] : #netgetdefaultautoselectfamilyattempttimeout
21172225[ `new net.Socket(options)` ] : #new-netsocketoptions
21182226[ `readable.setEncoding()` ] : stream.md#readablesetencodingencoding
2227+ [ `server.address()` ] : #serveraddress
21192228[ `server.close()` ] : #serverclosecallback
21202229[ `server.dropMaxConnection` ] : #serverdropmaxconnection
21212230[ `server.listen()` ] : #serverlisten
0 commit comments