node/test/parallel/test-dgram-bind-sync.js
Guy Bedford 723dd38882
dgram: add synchronous Socket.prototype.bindSync()
Provides sync non-blocking bind(2), without DNS lookup

Signed-off-by: Guy Bedford <guybedford@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/63838
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
2026-06-15 19:01:36 +00:00

172 lines
4.8 KiB
JavaScript

'use strict';
const common = require('../common');
const assert = require('assert');
const dgram = require('dgram');
// bindSync() binds synchronously and returns the resolved address, including
// the OS-assigned ephemeral port when port is 0.
{
const sock = dgram.createSocket('udp4');
const addr = sock.bindSync({ address: '127.0.0.1', port: 0 });
assert.strictEqual(addr.address, '127.0.0.1');
assert.strictEqual(addr.family, 'IPv4');
assert.strictEqual(typeof addr.port, 'number');
assert.ok(addr.port > 0);
// address() is valid synchronously and matches the returned address.
assert.deepStrictEqual(sock.address(), addr);
// The 'listening' event still fires on the next tick.
sock.on('listening', common.mustCall(() => sock.close()));
}
// Closing synchronously after bindSync() suppresses the deferred 'listening'.
{
const sock = dgram.createSocket('udp4');
sock.bindSync({ port: 0 });
sock.on('listening', common.mustNotCall());
sock.close();
}
// Defaults the address to the udp4 wildcard when omitted.
{
const sock = dgram.createSocket('udp4');
const addr = sock.bindSync();
assert.strictEqual(addr.address, '0.0.0.0');
assert.ok(addr.port > 0);
sock.close();
}
// 'message' events still flow asynchronously after a synchronous bind.
{
const receiver = dgram.createSocket('udp4');
const addr = receiver.bindSync({ address: '127.0.0.1', port: 0 });
receiver.on('message', common.mustCall((msg) => {
assert.strictEqual(msg.toString(), 'hello');
receiver.close();
}));
const sender = dgram.createSocket('udp4');
sender.send('hello', addr.port, '127.0.0.1', common.mustCall(() => {
sender.close();
}));
}
// Throws synchronously on EADDRINUSE.
{
const first = dgram.createSocket('udp4');
const addr = first.bindSync({ address: '127.0.0.1', port: 0 });
const second = dgram.createSocket('udp4');
assert.throws(() => {
second.bindSync({ address: '127.0.0.1', port: addr.port });
}, {
code: 'EADDRINUSE',
syscall: 'bind',
});
first.close();
second.close();
}
// Throws synchronously on a non-numeric address (no DNS resolution).
{
const sock = dgram.createSocket('udp4');
assert.throws(() => {
sock.bindSync({ address: 'localhost', port: 0 });
}, {
code: 'ERR_INVALID_ARG_VALUE',
name: 'TypeError',
});
sock.close();
}
// Rejects a non-string address.
{
const sock = dgram.createSocket('udp4');
assert.throws(() => sock.bindSync({ address: 12345 }), {
code: 'ERR_INVALID_ARG_TYPE',
});
sock.close();
}
// A rejected argument leaves the socket unbound and reusable.
{
const sock = dgram.createSocket('udp4');
assert.throws(() => sock.bindSync({ port: -1 }), {
code: 'ERR_SOCKET_BAD_PORT',
});
const addr = sock.bindSync({ port: 0 });
assert.ok(addr.port > 0);
sock.close();
}
// Throws when already bound.
{
const sock = dgram.createSocket('udp4');
sock.bindSync({ port: 0 });
assert.throws(() => sock.bindSync({ port: 0 }), {
code: 'ERR_SOCKET_ALREADY_BOUND',
});
sock.close();
}
// Rejects a non-object options argument.
{
const sock = dgram.createSocket('udp4');
assert.throws(() => sock.bindSync(0), { code: 'ERR_INVALID_ARG_TYPE' });
sock.close();
}
// udp6 wildcard default.
if (common.hasIPv6) {
const sock = dgram.createSocket('udp6');
const addr = sock.bindSync();
assert.strictEqual(addr.address, '::');
assert.strictEqual(addr.family, 'IPv6');
assert.ok(addr.port > 0);
sock.close();
}
// udp6 loopback with an OS-assigned ephemeral port, and async 'message' flow.
if (common.hasIPv6) {
const receiver = dgram.createSocket('udp6');
const addr = receiver.bindSync({ address: '::1', port: 0 });
assert.strictEqual(addr.address, '::1');
assert.strictEqual(addr.family, 'IPv6');
assert.ok(addr.port > 0);
assert.deepStrictEqual(receiver.address(), addr);
receiver.on('message', common.mustCall((msg) => {
assert.strictEqual(msg.toString(), 'hello');
receiver.close();
}));
const sender = dgram.createSocket('udp6');
sender.send('hello', addr.port, '::1', common.mustCall(() => {
sender.close();
}));
}
// A zone-indexed (scoped) IPv6 literal is accepted as a numeric IP; no DNS
// resolution occurs. Interface names are platform-specific, so this binds the
// scoped loopback only where the interface name is known (Linux: 'lo').
if (common.hasIPv6 && process.platform === 'linux') {
const sock = dgram.createSocket('udp6');
const addr = sock.bindSync({ address: '::1%lo', port: 0 });
assert.strictEqual(addr.address, '::1');
assert.strictEqual(addr.family, 'IPv6');
assert.ok(addr.port > 0);
sock.close();
}
// The ipv6Only flag is honored by the synchronous bind.
if (common.hasIPv6) {
const sock = dgram.createSocket({ type: 'udp6', ipv6Only: true });
const addr = sock.bindSync({ address: '::', port: 0 });
assert.strictEqual(addr.family, 'IPv6');
sock.close();
}