Skip to content

Commit c360339

Browse files
committed
bind error fix
1 parent d7cadc3 commit c360339

3 files changed

Lines changed: 40 additions & 11 deletions

File tree

‎doc/api/dgram.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,9 +461,12 @@ added: REPLACEME
461461

462462
The synchronous counterpart of [`socket.connect()`][]. For a UDP socket
463463
`connect(2)` only records the default peer address and is a local, non-blocking
464-
system call, so the association is performed inline and any error such as
465-
`ECONNREFUSED` is thrown synchronously rather than reported via the `'error'`
466-
event:
464+
system call, so the association is performed inline. Any error raised by the
465+
call itself (for example `EAFNOSUPPORT` for a mismatched address family) is
466+
thrown synchronously rather than reported via the `'error'` event. Because
467+
`connect(2)` does not probe reachability, errors such as `ECONNREFUSED` are
468+
still surfaced asynchronously on a later send or receive, exactly as for
469+
[`socket.connect()`][]:
467470

468471
```js
469472
const dgram = require('node:dgram');
@@ -477,7 +480,9 @@ If the socket is still unbound it is bound synchronously first. After
477480
`connectSync()` returns, [`socket.remoteAddress()`][] is valid synchronously
478481
and the `'connect'` event is emitted on the next tick. Trying to call
479482
`connectSync()` on an already connected socket throws an
480-
[`ERR_SOCKET_DGRAM_IS_CONNECTED`][] exception.
483+
[`ERR_SOCKET_DGRAM_IS_CONNECTED`][] exception, and calling it while an
484+
asynchronous [`socket.bind()`][] is still in progress throws an
485+
[`ERR_SOCKET_ALREADY_BOUND`][] exception.
481486

482487
`address` must be a numeric IP literal; `connectSync()` never performs DNS
483488
resolution (asynchronous name resolution being the only genuinely blocking part
@@ -1090,6 +1095,7 @@ and `udp6` sockets). The bound address and port can be retrieved using
10901095
[RFC 4007]: https://tools.ietf.org/html/rfc4007
10911096
[`'close'`]: #event-close
10921097
[`'message'`]: #event-message
1098+
[`ERR_SOCKET_ALREADY_BOUND`]: errors.md#err_socket_already_bound
10931099
[`ERR_SOCKET_BAD_PORT`]: errors.md#err_socket_bad_port
10941100
[`ERR_SOCKET_BUFFER_SIZE`]: errors.md#err_socket_buffer_size
10951101
[`ERR_SOCKET_DGRAM_IS_CONNECTED`]: errors.md#err_socket_dgram_is_connected

‎lib/dgram.js‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -507,13 +507,10 @@ Socket.prototype.connect = function(port, address, callback) {
507507
FunctionPrototypeCall(_connect, this, port, address, callback);
508508
};
509509

510-
// Synchronous counterpart of connect(). For a UDP socket connect(2) only sets
511-
// the default peer address and is a local, non-blocking operation, so this
512-
// connects inline and throws synchronously on error. The address must be a
513-
// numeric IP literal: asynchronous name resolution is the only genuinely
514-
// blocking part of connect(), so callers resolve names separately. If the
515-
// socket is still unbound it is bound synchronously first (see bindSync()).
516-
// The 'connect' event is emitted on the next tick.
510+
// Synchronous counterpart of connect(). connect(2) on a UDP socket only records
511+
// the default peer locally, so it runs inline and throws on errors raised by
512+
// the call itself; unreachable-peer errors stay asynchronous as for connect().
513+
// The address must be a numeric IP literal since DNS resolution is async.
517514
Socket.prototype.connectSync = function(port, address) {
518515
healthCheck(this);
519516
port = validatePort(port, 'Port', false);
@@ -538,6 +535,8 @@ Socket.prototype.connectSync = function(port, address) {
538535

539536
if (state.bindState === BIND_STATE_UNBOUND)
540537
this.bindSync();
538+
else if (state.bindState !== BIND_STATE_BOUND)
539+
throw new ERR_SOCKET_ALREADY_BOUND();
541540

542541
state.connectState = CONNECT_STATE_CONNECTING;
543542

‎test/parallel/test-dgram-connect-sync.js‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const common = require('../common');
33
const assert = require('assert');
44
const dgram = require('dgram');
5+
const net = require('net');
56

67
// connectSync() connects synchronously, binding the socket first when needed,
78
// and remoteAddress() is valid immediately.
@@ -115,6 +116,29 @@ const dgram = require('dgram');
115116
sock.close();
116117
}
117118

119+
// Throws synchronously for a blocked address, leaving the socket reusable.
120+
{
121+
const blockList = new net.BlockList();
122+
blockList.addAddress('127.0.0.1');
123+
const sock = dgram.createSocket({ type: 'udp4', sendBlockList: blockList });
124+
assert.throws(() => sock.connectSync(12345, '127.0.0.1'), {
125+
code: 'ERR_IP_BLOCKED',
126+
});
127+
sock.connectSync(12345, '127.0.0.2');
128+
assert.strictEqual(sock.remoteAddress().address, '127.0.0.2');
129+
sock.close();
130+
}
131+
132+
// Throws when an asynchronous bind() is still in progress.
133+
{
134+
const sock = dgram.createSocket('udp4');
135+
sock.bind(0, '127.0.0.1');
136+
assert.throws(() => sock.connectSync(12345, '127.0.0.1'), {
137+
code: 'ERR_SOCKET_ALREADY_BOUND',
138+
});
139+
sock.on('listening', common.mustCall(() => sock.close()));
140+
}
141+
118142
// udp6 loopback default.
119143
if (common.hasIPv6) {
120144
const sock = dgram.createSocket('udp6');

0 commit comments

Comments
 (0)