PageRenderTime 51ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/vock/socket.js

https://github.com/prodigeni/vock
JavaScript | 235 lines | 158 code | 32 blank | 45 comment | 13 complexity | a67ded291c3008122a106f18a970a69d MD5 | raw file
Possible License(s): BSD-3-Clause
  1. var util = require('util'),
  2. dgram = require('dgram'),
  3. net = require('net'),
  4. msgpack = require('msgpack-js'),
  5. pmp = require('nat-pmp'),
  6. natUpnp = require('nat-upnp'),
  7. netroute = require('netroute'),
  8. EventEmitter = require('events').EventEmitter;
  9. var socket = exports;
  10. //
  11. // ### function Socket (options)
  12. // #### @options {Object} Socket options
  13. // msgpack encoder/decoder wrapper over udp socket
  14. //
  15. function Socket(options) {
  16. EventEmitter.call(this);
  17. this.tsocket = null;
  18. this.socket = null;
  19. this.port = null;
  20. this.server = options.server;
  21. this.relayVersion = [0, 1];
  22. this.relaySeq = 0;
  23. this._initQueue = [];
  24. this._initialized = false;
  25. this.natTtl = 60 * 30;
  26. this.init();
  27. };
  28. util.inherits(Socket, EventEmitter);
  29. //
  30. // ### function create (options)
  31. // #### @options {Object} Socket options
  32. // Constructor wrapper
  33. //
  34. socket.create = function create(options) {
  35. return new Socket(options);
  36. };
  37. //
  38. // ### function bind (port, callback)
  39. // #### @ports {Array} Port numbers
  40. // #### @callback {Function} Continuation to proceed to
  41. Socket.prototype.bind = function bind(ports, callback) {
  42. var self = this,
  43. port = ports[0] || 0;
  44. if (this.tsocket) {
  45. this.tsocket.removeAllListeners('data');
  46. this.tsocket.removeAllListeners('error');
  47. this.tsocket.removeAllListeners('listening');
  48. try {
  49. this.tsocket.close();
  50. } catch(e) {
  51. }
  52. }
  53. // Bind TCP socket
  54. this.tsocket = net.createServer();
  55. this.tsocket.on('error', function(err) {
  56. // Try to bind to a random port
  57. self.bind(ports.slice(1), callback);
  58. });
  59. this.tsocket.listen(port, function() {
  60. self.tsocket.removeAllListeners('error');
  61. self.port = self.tsocket.address().port;
  62. // Bind UDP socket
  63. if (self.socket) {
  64. self.socket.removeAllListeners('message');
  65. self.socket.removeAllListeners('listening');
  66. try {
  67. self.socket.close();
  68. } catch(e) {
  69. }
  70. }
  71. self.socket = dgram.createSocket('udp4');
  72. self.socket.on('message', self.ondata.bind(self));
  73. self.socket.once('listening', function() {
  74. callback(self.port, port !== 0);
  75. });
  76. try {
  77. self.socket.bind(self.port);
  78. } catch (e) {
  79. self.bind(ports.slice(1), callback);
  80. }
  81. });
  82. };
  83. //
  84. // ### function init ()
  85. // Internal
  86. //
  87. Socket.prototype.init = function init() {
  88. var self = this;
  89. this.upnpClient = natUpnp.createClient();
  90. this.pmpClient = pmp.connect(netroute.getGateway());
  91. this.upnpClient.getMappings({
  92. local: true,
  93. description: /vock/i
  94. }, function(err, list) {
  95. list = list || [];
  96. self.bind(list.map(function(item) {
  97. return item.private.port;
  98. }), function(port, reuse) {
  99. self.emit('init', port);
  100. // Unwind accumulated callbacks
  101. var queue = self._initQueue;
  102. self._initialized = true;
  103. self._initQueue = [];
  104. queue.forEach(function(callback) {
  105. callback.call(self);
  106. });
  107. // Do not add mappings if we've found existing one
  108. if (reuse) {
  109. self.emit('nat:traversal:udp', 'upnp', port);
  110. self.emit('nat:traversal:tcp', 'upnp', port);
  111. return;
  112. }
  113. // Create port-forward mapping if possible
  114. var protocols = ['udp', 'tcp'];
  115. protocols.forEach(function(protocol) {
  116. self.upnpClient.portMapping({
  117. public: port,
  118. private: port,
  119. protocol: protocol,
  120. description: 'Vock - VoIP on node.js',
  121. ttl: 0 // Unlimited, since most routers doesn't support other value
  122. }, function(err) {
  123. if (err) return;
  124. self.emit('nat:traversal:' + protocol, 'upnp', port);
  125. });
  126. self.pmpClient.portMapping({
  127. private: port,
  128. public: port,
  129. type: protocol,
  130. ttl: self.natTtl
  131. }, function(err) {
  132. if (err) return;
  133. self.emit('nat:traversal:' + protocol, 'pmp', port);
  134. });
  135. });
  136. });
  137. });
  138. };
  139. //
  140. // ### function send (packet, target)
  141. // #### @packet {Object} packet
  142. // #### @target {Object} target address
  143. // Sends encoded packet to target
  144. //
  145. Socket.prototype.send = function(packet, target) {
  146. if (!this._initialized) {
  147. this._initQueue.push(function() {
  148. this.send(packet, target);
  149. });
  150. return;
  151. }
  152. try {
  153. var raw = msgpack.encode(packet);
  154. } catch (e) {
  155. this.emit('error', e);
  156. return;
  157. }
  158. this.socket.send(raw, 0, raw.length, target.port, target.address);
  159. };
  160. //
  161. // ### function relay (packet, id, target)
  162. // #### @packet {Object} packet
  163. // #### @id {String} room id
  164. // #### @target {Object} target address
  165. // Sends encoded packet to target through relay
  166. //
  167. Socket.prototype.relay = function relay(packet, id, target) {
  168. var wrapper = {
  169. protocol: 'relay',
  170. seq: this.relaySeq++,
  171. id: id,
  172. to: { address: target.address, port: target.port },
  173. body: packet,
  174. version: this.relayVersion
  175. };
  176. this.send(wrapper, this.server);
  177. };
  178. //
  179. // ### function ondata (raw, addr)
  180. // #### @raw {Buffer} packet
  181. // #### @addr {Object} from address
  182. // Receives incoming relay or direct packet
  183. //
  184. Socket.prototype.ondata = function ondata(raw, addr) {
  185. try {
  186. var msg = msgpack.decode(raw);
  187. } catch (e) {
  188. // Ignore decode errors
  189. return;
  190. }
  191. if (!msg) return this.emit('error', 'Empty message!');
  192. addr.relay = false;
  193. // Unwrap relay packets from server
  194. if (msg.protocol === 'relay' &&
  195. (addr.address == this.server.address ||
  196. this.server.address == '0.0.0.0') &&
  197. addr.port == this.server.port) {
  198. addr = msg.from;
  199. addr.relay = true;
  200. msg = msg.body;
  201. }
  202. this.emit('data', msg, addr);
  203. };