1 // Copyright Joyent, Inc. and other Node contributors.
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
22 var assert = require('assert');
23 var util = require('util');
24 var events = require('events');
26 var UDP = process.binding('udp_wrap').UDP;
28 var BIND_STATE_UNBOUND = 0;
29 var BIND_STATE_BINDING = 1;
30 var BIND_STATE_BOUND = 2;
37 var errnoException = util._errnoException;
44 function isIP(address) {
48 return net.isIP(address);
52 function lookup(address, family, callback) {
56 return dns.lookup(address, family, callback);
60 function lookup4(address, callback) {
61 return lookup(address || '0.0.0.0', 4, callback);
65 function lookup6(address, callback) {
66 return lookup(address || '::0', 6, callback);
70 function newHandle(type) {
73 handle.lookup = lookup4;
79 handle.lookup = lookup6;
80 handle.bind = handle.bind6;
81 handle.send = handle.send6;
85 if (type == 'unix_dgram')
86 throw new Error('unix_dgram sockets are not supported any more.');
88 throw new Error('Bad socket type specified. Valid types are: udp4, udp6');
92 exports._createSocketHandle = function(address, port, addressType, fd) {
93 // Opening an existing fd is not supported for UDP handles.
94 assert(!util.isNumber(fd) || fd < 0);
96 var handle = newHandle(addressType);
98 if (port || address) {
99 var err = handle.bind(address, port || 0, 0);
110 function Socket(type, listener) {
111 events.EventEmitter.call(this);
113 var handle = newHandle(type);
116 this._handle = handle;
117 this._receiving = false;
118 this._bindState = BIND_STATE_UNBOUND;
120 this.fd = null; // compatibility hack
122 if (util.isFunction(listener))
123 this.on('message', listener);
125 util.inherits(Socket, events.EventEmitter);
126 exports.Socket = Socket;
129 exports.createSocket = function(type, listener) {
130 return new Socket(type, listener);
134 function startListening(socket) {
135 socket._handle.onmessage = onMessage;
136 // Todo: handle errors
137 socket._handle.recvStart();
138 socket._receiving = true;
139 socket._bindState = BIND_STATE_BOUND;
140 socket.fd = -42; // compatibility hack
142 socket.emit('listening');
145 function replaceHandle(self, newHandle) {
147 // Set up the handle that we got from master.
148 newHandle.lookup = self._handle.lookup;
149 newHandle.bind = self._handle.bind;
150 newHandle.send = self._handle.send;
151 newHandle.owner = self;
153 // Replace the existing handle by the handle we got from master.
154 self._handle.close();
155 self._handle = newHandle;
158 Socket.prototype.bind = function(/*port, address, callback*/) {
163 if (this._bindState != BIND_STATE_UNBOUND)
164 throw new Error('Socket is already bound');
166 this._bindState = BIND_STATE_BINDING;
168 if (util.isFunction(arguments[arguments.length - 1]))
169 self.once('listening', arguments[arguments.length - 1]);
171 var UDP = process.binding('udp_wrap').UDP;
172 if (arguments[0] instanceof UDP) {
173 replaceHandle(self, arguments[0]);
174 startListening(self);
178 var port = arguments[0];
179 var address = arguments[1];
180 if (util.isFunction(address)) address = ''; // a.k.a. "any address"
182 // resolve address first
183 self._handle.lookup(address, function(err, ip) {
185 self._bindState = BIND_STATE_UNBOUND;
186 self.emit('error', err);
191 cluster = require('cluster');
193 if (cluster.isWorker) {
194 cluster._getServer(self, ip, port, self.type, -1, function(err, handle) {
196 self.emit('error', errnoException(err, 'bind'));
197 self._bindState = BIND_STATE_UNBOUND;
202 // handle has been closed in the mean time.
203 return handle.close();
205 replaceHandle(self, handle);
206 startListening(self);
211 return; // handle has been closed in the mean time
213 var err = self._handle.bind(ip, port || 0, /*flags=*/ 0);
215 self.emit('error', errnoException(err, 'bind'));
216 self._bindState = BIND_STATE_UNBOUND;
221 startListening(self);
227 // thin wrapper around `send`, here for compatibility with dgram_legacy.js
228 Socket.prototype.sendto = function(buffer,
234 if (!util.isNumber(offset) || !util.isNumber(length))
235 throw new Error('send takes offset and length as args 2 and 3');
237 if (!util.isString(address))
238 throw new Error(this.type + ' sockets must send to port, address');
240 this.send(buffer, offset, length, port, address, callback);
244 Socket.prototype.send = function(buffer,
252 if (!util.isBuffer(buffer))
253 throw new TypeError('First argument must be a buffer object.');
257 throw new RangeError('Offset should be >= 0');
259 if (offset >= buffer.length)
260 throw new RangeError('Offset into buffer too large');
262 // Sending a zero-length datagram is kind of pointless but it _is_
263 // allowed, hence check that length >= 0 rather than > 0.
266 throw new RangeError('Length should be >= 0');
268 if (offset + length > buffer.length)
269 throw new RangeError('Offset + length beyond buffer length');
272 if (port <= 0 || port > 65535)
273 throw new RangeError('Port should be > 0 and < 65536');
275 callback = callback || noop;
279 if (self._bindState == BIND_STATE_UNBOUND)
282 // If the socket hasn't been bound yet, push the outbound packet onto the
283 // send queue and send after binding is complete.
284 if (self._bindState != BIND_STATE_BOUND) {
285 // If the send queue hasn't been initialized yet, do it, and install an
286 // event handler that flushes the send queue after binding is done.
287 if (!self._sendQueue) {
288 self._sendQueue = [];
289 self.once('listening', function() {
290 // Flush the send queue.
291 for (var i = 0; i < self._sendQueue.length; i++)
292 self.send.apply(self, self._sendQueue[i]);
293 self._sendQueue = undefined;
296 self._sendQueue.push([buffer, offset, length, port, address, callback]);
300 self._handle.lookup(address, function(ex, ip) {
302 if (callback) callback(ex);
303 self.emit('error', ex);
305 else if (self._handle) {
306 var req = { cb: callback, oncomplete: afterSend };
307 var err = self._handle.send(req, buffer, offset, length, port, ip);
309 // don't emit as error, dgram_legacy.js compatibility
310 process.nextTick(function() {
311 callback(errnoException(err, 'send'));
319 function afterSend(err) {
321 this.cb(err ? errnoException(err, 'send') : null);
325 Socket.prototype.close = function() {
327 this._stopReceiving();
328 this._handle.close();
334 Socket.prototype.address = function() {
338 var err = this._handle.getsockname(out);
340 throw errnoException(err, 'getsockname');
347 Socket.prototype.setBroadcast = function(arg) {
348 var err = this._handle.setBroadcast(arg ? 1 : 0);
350 throw errnoException(err, 'setBroadcast');
355 Socket.prototype.setTTL = function(arg) {
356 if (!util.isNumber(arg)) {
357 throw new TypeError('Argument must be a number');
360 var err = this._handle.setTTL(arg);
362 throw errnoException(err, 'setTTL');
369 Socket.prototype.setMulticastTTL = function(arg) {
370 if (!util.isNumber(arg)) {
371 throw new TypeError('Argument must be a number');
374 var err = this._handle.setMulticastTTL(arg);
376 throw errnoException(err, 'setMulticastTTL');
383 Socket.prototype.setMulticastLoopback = function(arg) {
384 var err = this._handle.setMulticastLoopback(arg ? 1 : 0);
386 throw errnoException(err, 'setMulticastLoopback');
389 return arg; // 0.4 compatibility
393 Socket.prototype.addMembership = function(multicastAddress,
397 if (!multicastAddress) {
398 throw new Error('multicast address must be specified');
401 var err = this._handle.addMembership(multicastAddress, interfaceAddress);
403 throw new errnoException(err, 'addMembership');
408 Socket.prototype.dropMembership = function(multicastAddress,
412 if (!multicastAddress) {
413 throw new Error('multicast address must be specified');
416 var err = this._handle.dropMembership(multicastAddress, interfaceAddress);
418 throw new errnoException(err, 'dropMembership');
423 Socket.prototype._healthCheck = function() {
425 throw new Error('Not running'); // error message from dgram_legacy.js
429 Socket.prototype._stopReceiving = function() {
430 if (!this._receiving)
433 this._handle.recvStop();
434 this._receiving = false;
435 this.fd = null; // compatibility hack
439 function onMessage(nread, handle, buf, rinfo) {
440 var self = handle.owner;
442 return self.emit('error', errnoException(nread, 'recvmsg'));
444 rinfo.size = buf.length; // compatibility
445 self.emit('message', buf, rinfo);
449 Socket.prototype.ref = function() {
455 Socket.prototype.unref = function() {
457 this._handle.unref();