Skip to content

Commit 39ed68c

Browse files
authored
net: support sync connect for BoundSocket
When a net.Socket is constructed from a net.BoundSocket, support synchronous connect(). Signed-off-by: Guy Bedford <guybedford@gmail.com> PR-URL: #64375 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 34c28d5 commit 39ed68c

3 files changed

Lines changed: 53 additions & 0 deletions

File tree

doc/api/net.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,6 +1708,11 @@ Adoption transfers ownership of the socket; afterwards `address()` and `close()`
17081708
throw [`ERR_SOCKET_HANDLE_ADOPTED`][]. A handle that is never adopted must be
17091709
closed to avoid leaking the socket.
17101710

1711+
When an adopted `BoundSocket` connects to a numeric IP literal, `connect(2)` is
1712+
issued synchronously, so [`socket.localAddress`][] is resolved once
1713+
[`socket.connect()`][] returns. Connection failures are still reported via a
1714+
deferred `'error'` event.
1715+
17111716
```mjs
17121717
import net from 'node:net';
17131718

@@ -2293,6 +2298,7 @@ net.isIPv6('fhqwhgads'); // returns false
22932298
[`socket.connecting`]: #socketconnecting
22942299
[`socket.destroy()`]: #socketdestroyerror
22952300
[`socket.end()`]: #socketenddata-encoding-callback
2301+
[`socket.localAddress`]: #socketlocaladdress
22962302
[`socket.pause()`]: #socketpause
22972303
[`socket.resume()`]: #socketresume
22982304
[`socket.setEncoding()`]: #socketsetencodingencoding

lib/net.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,17 @@ function lookupAndConnect(self, options) {
16301630
// If host is an IP, skip performing a lookup
16311631
const addressType = isIP(host);
16321632
if (addressType) {
1633+
// An adopted BoundSocket needs no lookup: issue connect(2) in-tick so the
1634+
// kernel-resolved concrete source address is observable synchronously.
1635+
// Connection failures are still reported via a deferred 'error' event.
1636+
if (self[kBoundSource]) {
1637+
defaultTriggerAsyncIdScope(
1638+
self[async_id_symbol],
1639+
internalConnect,
1640+
self, host, port, addressType, localAddress, localPort,
1641+
);
1642+
return;
1643+
}
16331644
defaultTriggerAsyncIdScope(self[async_id_symbol], process.nextTick, () => {
16341645
if (self.connecting)
16351646
defaultTriggerAsyncIdScope(

test/parallel/test-net-boundsocket.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,42 @@ if (!common.isWindows && process.getuid() !== 0) {
178178
client.destroy();
179179
}
180180

181+
// Adopted BoundSocket: connect(2) is synchronous, so localAddress/localPort
182+
// resolve to the concrete source in-tick, before the 'connect' event.
183+
{
184+
const server = net.createServer();
185+
server.listen(0, '127.0.0.1', common.mustCall(() => {
186+
const serverPort = server.address().port;
187+
const bound = new net.BoundSocket({ host: '127.0.0.1', port: 0 });
188+
const localPort = bound.address().port;
189+
const client = new net.Socket({ handle: bound });
190+
191+
client.connect({ host: '127.0.0.1', port: serverPort });
192+
193+
assert.strictEqual(client.localAddress, '127.0.0.1');
194+
assert.strictEqual(client.localPort, localPort);
195+
196+
client.on('connect', common.mustCall(() => {
197+
assert.strictEqual(client.localAddress, '127.0.0.1');
198+
assert.strictEqual(client.localPort, localPort);
199+
client.destroy();
200+
server.close();
201+
}));
202+
}));
203+
}
204+
205+
// A connect failure is reported via a deferred 'error' event, never thrown
206+
// synchronously. An IPv4-bound handle connecting to an IPv6 literal fails on
207+
// address family mismatch.
208+
{
209+
const bound = new net.BoundSocket({ host: '127.0.0.1', port: 0 });
210+
const client = new net.Socket({ handle: bound });
211+
client.connect({ host: '::1', port: 1 });
212+
client.on('error', common.mustCall((err) => {
213+
assert.strictEqual(err.syscall, 'connect');
214+
}));
215+
}
216+
181217
// reusePort: SO_REUSEPORT permits multiple listeners on the same port. Support
182218
// is platform-dependent, so probe first.
183219
{

0 commit comments

Comments
 (0)