Skip to main content
Module

std/node/dgram.ts

Deno standard library
Go to Latest
File
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.// Copyright Joyent, Inc. and other Node contributors.//// Permission is hereby granted, free of charge, to any person obtaining a// copy of this software and associated documentation files (the// "Software"), to deal in the Software without restriction, including// without limitation the rights to use, copy, modify, merge, publish,// distribute, sublicense, and/or sell copies of the Software, and to permit// persons to whom the Software is furnished to do so, subject to the// following conditions://// The above copyright notice and this permission notice shall be included// in all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE// USE OR OTHER DEALINGS IN THE SOFTWARE.
import { Buffer } from "./buffer.ts";import { EventEmitter } from "./events.ts";import { lookup as defaultLookup } from "./dns.ts";import type { ErrnoException, NodeSystemErrorCtx } from "./internal/errors.ts";import { ERR_BUFFER_OUT_OF_BOUNDS, ERR_INVALID_ARG_TYPE, ERR_INVALID_FD_TYPE, ERR_MISSING_ARGS, ERR_SOCKET_ALREADY_BOUND, ERR_SOCKET_BAD_BUFFER_SIZE, ERR_SOCKET_BUFFER_SIZE, ERR_SOCKET_DGRAM_IS_CONNECTED, ERR_SOCKET_DGRAM_NOT_CONNECTED, ERR_SOCKET_DGRAM_NOT_RUNNING, errnoException, exceptionWithHostPort,} from "./internal/errors.ts";import type { Abortable } from "./_events.d.ts";import { kStateSymbol, newHandle } from "./internal/dgram.ts";import type { SocketType } from "./internal/dgram.ts";import { asyncIdSymbol, defaultTriggerAsyncIdScope, ownerSymbol,} from "./internal/async_hooks.ts";import { SendWrap, UDP } from "./internal_binding/udp_wrap.ts";import { isInt32, validateAbortSignal, validateNumber, validatePort, validateString,} from "./internal/validators.mjs";import { guessHandleType } from "./internal_binding/util.ts";import { os } from "./internal_binding/constants.ts";import { nextTick } from "./process.ts";import { isArrayBufferView } from "./internal/util/types.ts";
const { UV_UDP_REUSEADDR, UV_UDP_IPV6ONLY } = os;
const BIND_STATE_UNBOUND = 0;const BIND_STATE_BINDING = 1;const BIND_STATE_BOUND = 2;
const CONNECT_STATE_DISCONNECTED = 0;const CONNECT_STATE_CONNECTING = 1;const CONNECT_STATE_CONNECTED = 2;
const RECV_BUFFER = true;const SEND_BUFFER = false;
export interface AddressInfo { address: string; family: number; port: number;}
export type MessageType = string | Uint8Array | Buffer | DataView;
export type RemoteInfo = { address: string; family: "IPv4" | "IPv6"; port: number; size?: number;};
export interface BindOptions { port?: number; address?: string; exclusive?: boolean; fd?: number;}
export interface SocketOptions extends Abortable { type: SocketType; reuseAddr?: boolean; /** * @default false */ ipv6Only?: boolean; recvBufferSize?: number; sendBufferSize?: number; lookup?: typeof defaultLookup;}
interface SocketInternalState { handle: UDP | null; receiving: boolean; bindState: | typeof BIND_STATE_UNBOUND | typeof BIND_STATE_BINDING | typeof BIND_STATE_BOUND; connectState: | typeof CONNECT_STATE_DISCONNECTED | typeof CONNECT_STATE_CONNECTING | typeof CONNECT_STATE_CONNECTED; queue?: Array<() => void>; reuseAddr?: boolean; ipv6Only?: boolean; recvBufferSize?: number; sendBufferSize?: number;}
const isSocketOptions = ( socketOption: unknown,): socketOption is SocketOptions => socketOption !== null && typeof socketOption === "object";
const isUdpHandle = (handle: unknown): handle is UDP => handle !== null && typeof handle === "object" && typeof (handle as UDP).recvStart === "function";
const isBindOptions = (options: unknown): options is BindOptions => options !== null && typeof options === "object";
/** * Encapsulates the datagram functionality. * * New instances of `dgram.Socket` are created using `createSocket`. * The `new` keyword is not to be used to create `dgram.Socket` instances. */export class Socket extends EventEmitter { [asyncIdSymbol]!: number; [kStateSymbol]!: SocketInternalState;
type!: SocketType;
constructor( type: SocketType | SocketOptions, listener?: (msg: Buffer, rinfo: RemoteInfo) => void, ) { super();
let lookup; let recvBufferSize; let sendBufferSize;
let options: SocketOptions | undefined;
if (isSocketOptions(type)) { options = type; type = options.type; lookup = options.lookup; recvBufferSize = options.recvBufferSize; sendBufferSize = options.sendBufferSize; }
const handle = newHandle(type, lookup); handle[ownerSymbol] = this;
this[asyncIdSymbol] = handle.getAsyncId(); this.type = type;
if (typeof listener === "function") { this.on("message", listener); }
this[kStateSymbol] = { handle, receiving: false, bindState: BIND_STATE_UNBOUND, connectState: CONNECT_STATE_DISCONNECTED, queue: undefined, reuseAddr: options && options.reuseAddr, // Use UV_UDP_REUSEADDR if true. ipv6Only: options && options.ipv6Only, recvBufferSize, sendBufferSize, };
if (options?.signal !== undefined) { const { signal } = options;
validateAbortSignal(signal, "options.signal");
const onAborted = () => { this.close(); };
if (signal.aborted) { onAborted(); } else { signal.addEventListener("abort", onAborted);
this.once( "close", () => signal.removeEventListener("abort", onAborted), ); } } }
/** * Tells the kernel to join a multicast group at the given `multicastAddress` * and `multicastInterface` using the `IP_ADD_MEMBERSHIP` socket option. If * the`multicastInterface` argument is not specified, the operating system * will choose one interface and will add membership to it. To add membership * to every available interface, call `addMembership` multiple times, once * per interface. * * When called on an unbound socket, this method will implicitly bind to a * random port, listening on all interfaces. * * When sharing a UDP socket across multiple `cluster` workers, the * `socket.addMembership()` function must be called only once or an * `EADDRINUSE` error will occur: * * ```js * import cluster from 'cluster'; * import dgram from 'dgram'; * * if (cluster.isPrimary) { * cluster.fork(); // Works ok. * cluster.fork(); // Fails with EADDRINUSE. * } else { * const s = dgram.createSocket('udp4'); * s.bind(1234, () => { * s.addMembership('224.0.0.114'); * }); * } * ``` */ addMembership(multicastAddress: string, interfaceAddress?: string): void { healthCheck(this);
if (!multicastAddress) { throw new ERR_MISSING_ARGS("multicastAddress"); }
const { handle } = this[kStateSymbol]; const err = handle!.addMembership(multicastAddress, interfaceAddress);
if (err) { throw errnoException(err, "addMembership"); } }
/** * Tells the kernel to join a source-specific multicast channel at the given * `sourceAddress` and `groupAddress`, using the `multicastInterface` with * the `IP_ADD_SOURCE_MEMBERSHIP` socket option. If the `multicastInterface` * argument is not specified, the operating system will choose one interface * and will add membership to it. To add membership to every available * interface, call `socket.addSourceSpecificMembership()` multiple times, * once per interface. * * When called on an unbound socket, this method will implicitly bind to a * random port, listening on all interfaces. */ addSourceSpecificMembership( sourceAddress: string, groupAddress: string, interfaceAddress?: string, ): void { healthCheck(this);
validateString(sourceAddress, "sourceAddress"); validateString(groupAddress, "groupAddress");
const err = this[kStateSymbol].handle!.addSourceSpecificMembership( sourceAddress, groupAddress, interfaceAddress, );
if (err) { throw errnoException(err, "addSourceSpecificMembership"); } }
/** * Returns an object containing the address information for a socket. * For UDP sockets, this object will contain `address`, `family` and `port`properties. * * This method throws `EBADF` if called on an unbound socket. */ address(): AddressInfo { healthCheck(this);
const out = {}; const err = this[kStateSymbol].handle!.getsockname(out);
if (err) { throw errnoException(err, "getsockname"); }
return out as AddressInfo; }
/** * For UDP sockets, causes the `dgram.Socket` to listen for datagram * messages on a named `port` and optional `address`. If `port` is not * specified or is `0`, the operating system will attempt to bind to a * random port. If `address` is not specified, the operating system will * attempt to listen on all addresses. Once binding is complete, a * `'listening'` event is emitted and the optional `callback` function is * called. * * Specifying both a `'listening'` event listener and passing a `callback` to * the `socket.bind()` method is not harmful but not very useful. * * A bound datagram socket keeps the Node.js process running to receive * datagram messages. * * If binding fails, an `'error'` event is generated. In rare case (e.g. * attempting to bind with a closed socket), an `Error` may be thrown. * * Example of a UDP server listening on port 41234: * * ```js * import dgram from 'dgram'; * * const server = dgram.createSocket('udp4'); * * server.on('error', (err) => { * console.log(`server error:\n${err.stack}`); * server.close(); * }); * * server.on('message', (msg, rinfo) => { * console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`); * }); * * server.on('listening', () => { * const address = server.address(); * console.log(`server listening ${address.address}:${address.port}`); * }); * * server.bind(41234); * // Prints: server listening 0.0.0.0:41234 * ``` * * @param callback with no parameters. Called when binding is complete. */ bind(port?: number, address?: string, callback?: () => void): this; bind(port: number, callback?: () => void): this; bind(callback: () => void): this; bind(options: BindOptions, callback?: () => void): this; bind(port_?: unknown, address_?: unknown /* callback */): this { let port = typeof port_ === "function" ? null : port_;
healthCheck(this);
const state = this[kStateSymbol];
if (state.bindState !== BIND_STATE_UNBOUND) { throw new ERR_SOCKET_ALREADY_BOUND(); }
state.bindState = BIND_STATE_BINDING;
const cb = arguments.length && arguments[arguments.length - 1];
if (typeof cb === "function") { // deno-lint-ignore no-inner-declarations function removeListeners(this: Socket) { this.removeListener("error", removeListeners); this.removeListener("listening", onListening); }
// deno-lint-ignore no-inner-declarations function onListening(this: Socket) { removeListeners.call(this); cb.call(this); }
this.on("error", removeListeners); this.on("listening", onListening); }
if (isUdpHandle(port)) { replaceHandle(this, port); startListening(this);
return this; }
// Open an existing fd instead of creating a new one. if (isBindOptions(port) && isInt32(port.fd!) && port.fd! > 0) { const fd = port.fd!; const state = this[kStateSymbol];
// TODO(cmorten): here we deviate somewhat from the Node implementation which // makes use of the https://nodejs.org/api/cluster.html module to run servers // across a "cluster" of Node processes to take advantage of multi-core // systems. // // Though Deno has has a Worker capability from which we could simulate this, // for now we assert that we are _always_ on the primary process.
const type = guessHandleType(fd);
if (type !== "UDP") { throw new ERR_INVALID_FD_TYPE(type); }
const err = state.handle!.open(fd);
if (err) { throw errnoException(err, "open"); }
startListening(this);
return this; }
let address: string;
if (isBindOptions(port)) { address = port.address || ""; port = port.port; } else { address = typeof address_ === "function" ? "" : (address_ as string); }
// Defaulting address for bind to all interfaces if (!address) { if (this.type === "udp4") { address = "0.0.0.0"; } else { address = "::"; } }
// Resolve address first state.handle!.lookup(address, (lookupError, ip) => { if (lookupError) { state.bindState = BIND_STATE_UNBOUND; this.emit("error", lookupError);
return; }
let flags = 0;
if (state.reuseAddr) { flags |= UV_UDP_REUSEADDR; } if (state.ipv6Only) { flags |= UV_UDP_IPV6ONLY; }
// TODO(cmorten): here we deviate somewhat from the Node implementation which // makes use of the https://nodejs.org/api/cluster.html module to run servers // across a "cluster" of Node processes to take advantage of multi-core // systems. // // Though Deno has has a Worker capability from which we could simulate this, // for now we assert that we are _always_ on the primary process.
if (!state.handle) { return; // Handle has been closed in the mean time }
const err = state.handle.bind(ip, port as number || 0, flags);
if (err) { const ex = exceptionWithHostPort(err, "bind", ip, port as number); state.bindState = BIND_STATE_UNBOUND; this.emit("error", ex);
// Todo: close? return; }
startListening(this); });
return this; }
/** * Close the underlying socket and stop listening for data on it. If a * callback is provided, it is added as a listener for the `'close'` event. * * @param callback Called when the socket has been closed. */ close(callback?: () => void): this { const state = this[kStateSymbol]; const queue = state.queue;
if (typeof callback === "function") { this.on("close", callback); }
if (queue !== undefined) { queue.push(this.close.bind(this));
return this; }
healthCheck(this); stopReceiving(this);
state.handle!.close(); state.handle = null;
defaultTriggerAsyncIdScope( this[asyncIdSymbol], nextTick, socketCloseNT, this, );
return this; }
/** * Associates the `dgram.Socket` to a remote address and port. Every * message sent by this handle is automatically sent to that destination. * Also, the socket will only receive messages from that remote peer. * Trying to call `connect()` on an already connected socket will result * in an `ERR_SOCKET_DGRAM_IS_CONNECTED` exception. If `address` is not * provided, `'127.0.0.1'` (for `udp4` sockets) or `'::1'` (for `udp6` sockets) * will be used by default. Once the connection is complete, a `'connect'` event * is emitted and the optional `callback` function is called. In case of failure, * the `callback` is called or, failing this, an `'error'` event is emitted. * * @param callback Called when the connection is completed or on error. */ connect( port: number, address?: string, callback?: (err?: ErrnoException) => void, ): void; connect(port: number, callback: (err?: ErrnoException) => void): void; connect(port: number, address?: unknown, callback?: unknown): void { port = validatePort(port, "Port", false);
if (typeof address === "function") { callback = address; address = ""; } else if (address === undefined) { address = ""; }
validateString(address, "address");
const state = this[kStateSymbol];
if (state.connectState !== CONNECT_STATE_DISCONNECTED) { throw new ERR_SOCKET_DGRAM_IS_CONNECTED(); }
state.connectState = CONNECT_STATE_CONNECTING;
if (state.bindState === BIND_STATE_UNBOUND) { this.bind({ port: 0, exclusive: true }); }
if (state.bindState !== BIND_STATE_BOUND) { enqueue( this, _connect.bind( this, port, address as string, callback as (err?: ErrnoException) => void, ), );
return; }
Reflect.apply(_connect, this, [port, address, callback]); }
/** * A synchronous function that disassociates a connected `dgram.Socket` from * its remote address. Trying to call `disconnect()` on an unbound or already * disconnected socket will result in an `ERR_SOCKET_DGRAM_NOT_CONNECTED` * exception. */ disconnect(): void { const state = this[kStateSymbol];
if (state.connectState !== CONNECT_STATE_CONNECTED) { throw new ERR_SOCKET_DGRAM_NOT_CONNECTED(); }
const err = state.handle!.disconnect();
if (err) { throw errnoException(err, "connect"); } else { state.connectState = CONNECT_STATE_DISCONNECTED; } }
/** * Instructs the kernel to leave a multicast group at `multicastAddress` * using the `IP_DROP_MEMBERSHIP` socket option. This method is automatically * called by the kernel when the socket is closed or the process terminates, * so most apps will never have reason to call this. * * If `multicastInterface` is not specified, the operating system will * attempt to drop membership on all valid interfaces. */ dropMembership(multicastAddress: string, interfaceAddress?: string): void { healthCheck(this);
if (!multicastAddress) { throw new ERR_MISSING_ARGS("multicastAddress"); }
const err = this[kStateSymbol].handle!.dropMembership( multicastAddress, interfaceAddress, );
if (err) { throw errnoException(err, "dropMembership"); } }
/** * Instructs the kernel to leave a source-specific multicast channel at the * given `sourceAddress` and `groupAddress` using the * `IP_DROP_SOURCE_MEMBERSHIP` socket option. This method is automatically * called by the kernel when the socket is closed or the process terminates, * so most apps will never have reason to call this. * * If `multicastInterface` is not specified, the operating system will * attempt to drop membership on all valid interfaces. */ dropSourceSpecificMembership( sourceAddress: string, groupAddress: string, interfaceAddress?: string, ): void { healthCheck(this);
validateString(sourceAddress, "sourceAddress"); validateString(groupAddress, "groupAddress");
const err = this[kStateSymbol].handle!.dropSourceSpecificMembership( sourceAddress, groupAddress, interfaceAddress, );
if (err) { throw errnoException(err, "dropSourceSpecificMembership"); } }
/** * This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound * socket. * * @return the `SO_RCVBUF` socket receive buffer size in bytes. */ getRecvBufferSize(): number { return bufferSize(this, 0, RECV_BUFFER); }
/** * This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound * socket. * * @return the `SO_SNDBUF` socket send buffer size in bytes. */ getSendBufferSize(): number { return bufferSize(this, 0, SEND_BUFFER); }
/** * By default, binding a socket will cause it to block the Node.js process * from exiting as long as the socket is open. The `socket.unref()` method * can be used to exclude the socket from the reference counting that keeps * the Node.js process active. The `socket.ref()` method adds the socket back * to the reference counting and restores the default behavior. * * Calling `socket.ref()` multiples times will have no additional effect. * * The `socket.ref()` method returns a reference to the socket so calls can * be chained. */ ref(): this { const handle = this[kStateSymbol].handle;
if (handle) { handle.ref(); }
return this; }
/** * Returns an object containing the `address`, `family`, and `port` of the * remote endpoint. This method throws an `ERR_SOCKET_DGRAM_NOT_CONNECTED` * exception if the socket is not connected. */ remoteAddress(): AddressInfo { healthCheck(this);
const state = this[kStateSymbol];
if (state.connectState !== CONNECT_STATE_CONNECTED) { throw new ERR_SOCKET_DGRAM_NOT_CONNECTED(); }
const out = {}; const err = state.handle!.getpeername(out);
if (err) { throw errnoException(err, "getpeername"); }
return out as AddressInfo; }
/** * Broadcasts a datagram on the socket. * For connectionless sockets, the destination `port` and `address` must be * specified. Connected sockets, on the other hand, will use their associated * remote endpoint, so the `port` and `address` arguments must not be set. * * The `msg` argument contains the message to be sent. * Depending on its type, different behavior can apply. If `msg` is a * `Buffer`, any `TypedArray` or a `DataView`, * the `offset` and `length` specify the offset within the `Buffer` where the * message begins and the number of bytes in the message, respectively. * If `msg` is a `String`, then it is automatically converted to a `Buffer` * with `'utf8'` encoding. With messages that contain multi-byte characters, * `offset` and `length` will be calculated with respect to `byte length` and * not the character position. If `msg` is an array, `offset` and `length` * must not be specified. * * The `address` argument is a string. If the value of `address` is a host * name, DNS will be used to resolve the address of the host. If `address` * is not provided or otherwise nullish, `'127.0.0.1'` (for `udp4` sockets) * or `'::1'`(for `udp6` sockets) will be used by default. * * If the socket has not been previously bound with a call to `bind`, the * socket is assigned a random port number and is bound to the "all * interfaces" address (`'0.0.0.0'` for `udp4` sockets, `'::0'` for `udp6` * sockets.) * * An optional `callback` function may be specified to as a way of * reporting DNS errors or for determining when it is safe to reuse the `buf` * object. DNS lookups delay the time to send for at least one tick of the * Node.js event loop. * * The only way to know for sure that the datagram has been sent is by using * a `callback`. If an error occurs and a `callback` is given, the error will * be passed as the first argument to the `callback`. If a `callback` is not * given, the error is emitted as an `'error'` event on the `socket` object. * * Offset and length are optional but both _must_ be set if either are used. * They are supported only when the first argument is a `Buffer`, a * `TypedArray`, or a `DataView`. * * This method throws `ERR_SOCKET_BAD_PORT` if called on an unbound socket. * * Example of sending a UDP packet to a port on `localhost`; * * ```js * import dgram from 'dgram'; * import { Buffer } from 'buffer'; * * const message = Buffer.from('Some bytes'); * const client = dgram.createSocket('udp4'); * client.send(message, 41234, 'localhost', (err) => { * client.close(); * }); * ``` * * Example of sending a UDP packet composed of multiple buffers to a port on * `127.0.0.1`; * * ```js * import dgram from 'dgram'; * import { Buffer } from 'buffer'; * * const buf1 = Buffer.from('Some '); * const buf2 = Buffer.from('bytes'); * const client = dgram.createSocket('udp4'); * client.send([buf1, buf2], 41234, (err) => { * client.close(); * }); * ``` * * Sending multiple buffers might be faster or slower depending on the * application and operating system. Run benchmarks to * determine the optimal strategy on a case-by-case basis. Generally * speaking, however, sending multiple buffers is faster. * * Example of sending a UDP packet using a socket connected to a port on * `localhost`: * * ```js * import dgram from 'dgram'; * import { Buffer } from 'buffer'; * * const message = Buffer.from('Some bytes'); * const client = dgram.createSocket('udp4'); * client.connect(41234, 'localhost', (err) => { * client.send(message, (err) => { * client.close(); * }); * }); * ``` * * @param msg Message to be sent. * @param offset Offset in the buffer where the message starts. * @param length Number of bytes in the message. * @param port Destination port. * @param address Destination host name or IP address. * @param callback Called when the message has been sent. */ send( msg: MessageType | ReadonlyArray<MessageType>, port?: number, address?: string, callback?: (error: ErrnoException | null, bytes?: number) => void, ): void; send( msg: MessageType | ReadonlyArray<MessageType>, port?: number, callback?: (error: ErrnoException | null, bytes?: number) => void, ): void; send( msg: MessageType | ReadonlyArray<MessageType>, callback?: (error: ErrnoException | null, bytes?: number) => void, ): void; send( msg: MessageType, offset: number, length: number, port?: number, address?: string, callback?: (error: ErrnoException | null, bytes?: number) => void, ): void; send( msg: MessageType, offset: number, length: number, port?: number, callback?: (error: ErrnoException | null, bytes?: number) => void, ): void; send( msg: MessageType, offset: number, length: number, callback?: (error: ErrnoException | null, bytes?: number) => void, ): void; send( buffer: unknown, offset?: unknown, length?: unknown, port?: unknown, address?: unknown, callback?: unknown, ): void { let list: MessageType[] | null;
const state = this[kStateSymbol]; const connected = state.connectState === CONNECT_STATE_CONNECTED;
if (!connected) { if (address || (port && typeof port !== "function")) { buffer = sliceBuffer( buffer as MessageType, offset as number, length as number, ); } else { callback = port; port = offset; address = length; } } else { if (typeof length === "number") { buffer = sliceBuffer(buffer as MessageType, offset as number, length);
if (typeof port === "function") { callback = port; port = null; } } else { callback = offset; }
if (port || address) { throw new ERR_SOCKET_DGRAM_IS_CONNECTED(); } }
if (!Array.isArray(buffer)) { if (typeof buffer === "string") { list = [Buffer.from(buffer)]; } else if (!isArrayBufferView(buffer)) { throw new ERR_INVALID_ARG_TYPE( "buffer", ["Buffer", "TypedArray", "DataView", "string"], buffer, ); } else { list = [buffer as MessageType]; } } else if (!(list = fixBufferList(buffer))) { throw new ERR_INVALID_ARG_TYPE( "buffer list arguments", ["Buffer", "TypedArray", "DataView", "string"], buffer, ); }
if (!connected) { port = validatePort(port, "Port", false); }
// Normalize callback so it's either a function or undefined but not anything // else. if (typeof callback !== "function") { callback = undefined; }
if (typeof address === "function") { callback = address; address = undefined; } else if (address && typeof address !== "string") { throw new ERR_INVALID_ARG_TYPE("address", ["string", "falsy"], address); }
healthCheck(this);
if (state.bindState === BIND_STATE_UNBOUND) { this.bind({ port: 0, exclusive: true }); }
if (list.length === 0) { list.push(Buffer.alloc(0)); }
// If the socket hasn't been bound yet, push the outbound packet onto the // send queue and send after binding is complete. if (state.bindState !== BIND_STATE_BOUND) { // @ts-ignore mapping unknowns back onto themselves doesn't type nicely enqueue(this, this.send.bind(this, list, port, address, callback));
return; }
const afterDns = (ex: ErrnoException | null, ip: string) => { defaultTriggerAsyncIdScope( this[asyncIdSymbol], doSend, ex, this, ip, list, address, port, callback, ); };
if (!connected) { state.handle!.lookup(address as string, afterDns); } else { afterDns(null, ""); } }
/** * Sets or clears the `SO_BROADCAST` socket option. When set to `true`, UDP * packets may be sent to a local interface's broadcast address. * * This method throws `EBADF` if called on an unbound socket. */ setBroadcast(arg: boolean): void { const err = this[kStateSymbol].handle!.setBroadcast(arg ? 1 : 0);
if (err) { throw errnoException(err, "setBroadcast"); } }
/** * _All references to scope in this section are referring to [IPv6 Zone Indices](https://en.wikipedia.org/wiki/IPv6_address#Scoped_literal_IPv6_addresses), which are defined by [RFC * 4007](https://tools.ietf.org/html/rfc4007). In string form, an IP_ * _with a scope index is written as `'IP%scope'` where scope is an interface name_ * _or interface number._ * * Sets the default outgoing multicast interface of the socket to a chosen * interface or back to system interface selection. The `multicastInterface` must * be a valid string representation of an IP from the socket's family. * * For IPv4 sockets, this should be the IP configured for the desired physical * interface. All packets sent to multicast on the socket will be sent on the * interface determined by the most recent successful use of this call. * * For IPv6 sockets, `multicastInterface` should include a scope to indicate the * interface as in the examples that follow. In IPv6, individual `send` calls can * also use explicit scope in addresses, so only packets sent to a multicast * address without specifying an explicit scope are affected by the most recent * successful use of this call. * * This method throws `EBADF` if called on an unbound socket. * * #### Example: IPv6 outgoing multicast interface * * On most systems, where scope format uses the interface name: * * ```js * const socket = dgram.createSocket('udp6'); * * socket.bind(1234, () => { * socket.setMulticastInterface('::%eth1'); * }); * ``` * * On Windows, where scope format uses an interface number: * * ```js * const socket = dgram.createSocket('udp6'); * * socket.bind(1234, () => { * socket.setMulticastInterface('::%2'); * }); * ``` * * #### Example: IPv4 outgoing multicast interface * * All systems use an IP of the host on the desired physical interface: * * ```js * const socket = dgram.createSocket('udp4'); * * socket.bind(1234, () => { * socket.setMulticastInterface('10.0.0.2'); * }); * ``` */ setMulticastInterface(interfaceAddress: string): void { healthCheck(this); validateString(interfaceAddress, "interfaceAddress");
const err = this[kStateSymbol].handle!.setMulticastInterface( interfaceAddress, );
if (err) { throw errnoException(err, "setMulticastInterface"); } }
/** * Sets or clears the `IP_MULTICAST_LOOP` socket option. When set to `true`, * multicast packets will also be received on the local interface. * * This method throws `EBADF` if called on an unbound socket. */ setMulticastLoopback(arg: boolean): typeof arg { const err = this[kStateSymbol].handle!.setMulticastLoopback(arg ? 1 : 0);
if (err) { throw errnoException(err, "setMulticastLoopback"); }
return arg; // 0.4 compatibility }
/** * Sets the `IP_MULTICAST_TTL` socket option. While TTL generally stands for * "Time to Live", in this context it specifies the number of IP hops that a * packet is allowed to travel through, specifically for multicast traffic. Each * router or gateway that forwards a packet decrements the TTL. If the TTL is * decremented to 0 by a router, it will not be forwarded. * * The `ttl` argument may be between 0 and 255\. The default on most systems is `1`. * * This method throws `EBADF` if called on an unbound socket. */ setMulticastTTL(ttl: number): typeof ttl { validateNumber(ttl, "ttl");
const err = this[kStateSymbol].handle!.setMulticastTTL(ttl);
if (err) { throw errnoException(err, "setMulticastTTL"); }
return ttl; }
/** * Sets the `SO_RCVBUF` socket option. Sets the maximum socket receive buffer * in bytes. * * This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound socket. */ setRecvBufferSize(size: number): void { bufferSize(this, size, RECV_BUFFER); }
/** * Sets the `SO_SNDBUF` socket option. Sets the maximum socket send buffer * in bytes. * * This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound socket. */ setSendBufferSize(size: number): void { bufferSize(this, size, SEND_BUFFER); }
/** * Sets the `IP_TTL` socket option. While TTL generally stands for "Time to Live", * in this context it specifies the number of IP hops that a packet is allowed to * travel through. Each router or gateway that forwards a packet decrements the * TTL. If the TTL is decremented to 0 by a router, it will not be forwarded. * Changing TTL values is typically done for network probes or when multicasting. * * The `ttl` argument may be between between 1 and 255\. The default on most systems * is 64. * * This method throws `EBADF` if called on an unbound socket. */ setTTL(ttl: number): typeof ttl { validateNumber(ttl, "ttl");
const err = this[kStateSymbol].handle!.setTTL(ttl);
if (err) { throw errnoException(err, "setTTL"); }
return ttl; }
/** * By default, binding a socket will cause it to block the Node.js process from * exiting as long as the socket is open. The `socket.unref()` method can be used * to exclude the socket from the reference counting that keeps the Node.js * process active, allowing the process to exit even if the socket is still * listening. * * Calling `socket.unref()` multiple times will have no addition effect. * * The `socket.unref()` method returns a reference to the socket so calls can be * chained. */ unref(): this { const handle = this[kStateSymbol].handle;
if (handle) { handle.unref(); }
return this; }}
/** * Creates a `dgram.Socket` object. Once the socket is created, calling * `socket.bind()` will instruct the socket to begin listening for datagram * messages. When `address` and `port` are not passed to `socket.bind()` the * method will bind the socket to the "all interfaces" address on a random port * (it does the right thing for both `udp4` and `udp6` sockets). The bound * address and port can be retrieved using `socket.address().address` and * `socket.address().port`. * * If the `signal` option is enabled, calling `.abort()` on the corresponding * `AbortController` is similar to calling `.close()` on the socket: * * ```js * const controller = new AbortController(); * const { signal } = controller; * const server = dgram.createSocket({ type: 'udp4', signal }); * server.on('message', (msg, rinfo) => { * console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`); * }); * // Later, when you want to close the server. * controller.abort(); * ``` * * @param options * @param callback Attached as a listener for `'message'` events. Optional. */export function createSocket( type: SocketType, listener?: (msg: Buffer, rinfo: RemoteInfo) => void,): Socket;export function createSocket( type: SocketOptions, listener?: (msg: Buffer, rinfo: RemoteInfo) => void,): Socket;export function createSocket( type: SocketType | SocketOptions, listener?: (msg: Buffer, rinfo: RemoteInfo) => void,): Socket { return new Socket(type, listener);}
function startListening(socket: Socket): void { const state = socket[kStateSymbol];
state.handle!.onmessage = onMessage; // Todo: handle errors state.handle!.recvStart(); state.receiving = true; state.bindState = BIND_STATE_BOUND;
if (state.recvBufferSize) { bufferSize(socket, state.recvBufferSize, RECV_BUFFER); }
if (state.sendBufferSize) { bufferSize(socket, state.sendBufferSize, SEND_BUFFER); }
socket.emit("listening");}
function replaceHandle(self: Socket, newHandle: UDP): void { const state = self[kStateSymbol]; const oldHandle = state.handle!;
// Set up the handle that we got from primary. newHandle.lookup = oldHandle.lookup; newHandle.bind = oldHandle.bind; newHandle.send = oldHandle.send; newHandle[ownerSymbol] = self;
// Replace the existing handle by the handle we got from primary. oldHandle.close(); state.handle = newHandle;}
function bufferSize(self: Socket, size: number, buffer: boolean): number { if (size >>> 0 !== size) { throw new ERR_SOCKET_BAD_BUFFER_SIZE(); }
const ctx = {}; const ret = self[kStateSymbol].handle!.bufferSize(size, buffer, ctx);
if (ret === undefined) { throw new ERR_SOCKET_BUFFER_SIZE(ctx as NodeSystemErrorCtx); }
return ret;}
function socketCloseNT(self: Socket): void { self.emit("close");}
function healthCheck(socket: Socket): void { if (!socket[kStateSymbol].handle) { // Error message from dgram_legacy.js. throw new ERR_SOCKET_DGRAM_NOT_RUNNING(); }}
function stopReceiving(socket: Socket): void { const state = socket[kStateSymbol];
if (!state.receiving) { return; }
state.handle!.recvStop(); state.receiving = false;}
function onMessage( nread: number, handle: UDP, buf?: Buffer, rinfo?: RemoteInfo,): void { const self = handle[ownerSymbol] as Socket;
if (nread < 0) { self.emit("error", errnoException(nread, "recvmsg"));
return; }
rinfo!.size = buf!.length; // compatibility
self.emit("message", buf, rinfo);}
function sliceBuffer(buffer: MessageType, offset: number, length: number) { if (typeof buffer === "string") { buffer = Buffer.from(buffer); } else if (!isArrayBufferView(buffer)) { throw new ERR_INVALID_ARG_TYPE( "buffer", ["Buffer", "TypedArray", "DataView", "string"], buffer, ); }
offset = offset >>> 0; length = length >>> 0;
if (offset > buffer.byteLength) { throw new ERR_BUFFER_OUT_OF_BOUNDS("offset"); }
if (offset + length > buffer.byteLength) { throw new ERR_BUFFER_OUT_OF_BOUNDS("length"); }
return Buffer.from(buffer.buffer, buffer.byteOffset + offset, length);}
function fixBufferList( list: ReadonlyArray<MessageType>,): Array<MessageType> | null { const newList = new Array(list.length);
for (let i = 0, l = list.length; i < l; i++) { const buf = list[i];
if (typeof buf === "string") { newList[i] = Buffer.from(buf); } else if (!isArrayBufferView(buf)) { return null; } else { newList[i] = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); } }
return newList;}
function enqueue(self: Socket, toEnqueue: () => void) { const state = self[kStateSymbol];
// If the send queue hasn't been initialized yet, do it, and install an // event handler that flushes the send queue after binding is done. if (state.queue === undefined) { state.queue = [];
self.once(EventEmitter.errorMonitor, onListenError); self.once("listening", onListenSuccess); }
state.queue.push(toEnqueue);}
function onListenSuccess(this: Socket): void { this.removeListener(EventEmitter.errorMonitor, onListenError); clearQueue.call(this);}
function onListenError(this: Socket): void { this.removeListener("listening", onListenSuccess); this[kStateSymbol].queue = undefined;}
function clearQueue(this: Socket): void { const state = this[kStateSymbol]; const queue = state.queue; state.queue = undefined;
// Flush the send queue. for (const queueEntry of queue!) { queueEntry(); }}
function _connect( this: Socket, port: number, address: string, callback: (err?: ErrnoException) => void,) { const state = this[kStateSymbol];
if (callback) { this.once("connect", callback); }
const afterDns = (ex: ErrnoException | null, ip: string) => { defaultTriggerAsyncIdScope( this[asyncIdSymbol], doConnect, ex, this, ip, address, port, callback, ); };
state.handle!.lookup(address, afterDns);}
function doConnect( ex: ErrnoException | null, self: Socket, ip: string, address: string, port: number, callback: (err?: ErrnoException) => void,) { const state = self[kStateSymbol];
if (!state.handle) { return; }
if (!ex) { const err = state.handle.connect(ip, port);
if (err) { ex = exceptionWithHostPort(err, "connect", address, port); } }
if (ex) { state.connectState = CONNECT_STATE_DISCONNECTED;
return nextTick(() => { if (callback) { self.removeListener("connect", callback);
callback(ex!); } else { self.emit("error", ex); } }); }
state.connectState = CONNECT_STATE_CONNECTED;
nextTick(() => self.emit("connect"));}
function doSend( ex: ErrnoException | null, self: Socket, ip: string, list: MessageType[], address: string, port: number, callback?: (error: ErrnoException | null, bytes?: number) => void,) { const state = self[kStateSymbol];
if (ex) { if (typeof callback === "function") { nextTick(callback, ex);
return; }
nextTick(() => self.emit("error", ex));
return; } else if (!state.handle) { return; }
const req = new SendWrap(); req.list = list; // Keep reference alive. req.address = address; req.port = port;
if (callback) { req.callback = callback; req.oncomplete = afterSend; }
let err;
if (port) { err = state.handle.send(req, list, list.length, port, ip, !!callback); } else { err = state.handle.send(req, list, list.length, !!callback); }
if (err >= 1) { // Synchronous finish. The return code is msg_length + 1 so that we can // distinguish between synchronous success and asynchronous success. if (callback) { nextTick(callback, null, err - 1); }
return; }
if (err && callback) { // Don't emit as error, dgram_legacy.js compatibility const ex = exceptionWithHostPort(err, "send", address, port);
nextTick(callback, ex); }}
function afterSend(this: SendWrap, err: number | null, sent?: number) { let ex: ErrnoException | null;
if (err) { ex = exceptionWithHostPort(err, "send", this.address, this.port); } else { ex = null; }
this.callback(ex, sent);}
export type { SocketType };
export default { createSocket, Socket,};