├── lib ├── attribute │ ├── UseCandidate.js │ ├── password.js │ ├── padding.js │ ├── otherAddress.js │ ├── MappedAddress.js │ ├── reflectedFrom.js │ ├── sourceAddress.js │ ├── changedAddress.js │ ├── responseOrigin.js │ ├── AlternateServer.js │ ├── responseAddress.js │ ├── Priority.js │ ├── responsePort.js │ ├── Username.js │ ├── IceControlled.js │ ├── IceControlling.js │ ├── Fingerprint.js │ ├── Nonce.js │ ├── Realm.js │ ├── Software.js │ ├── changeRequest.js │ ├── UnknownAttributes.js │ ├── XORMappedAddress.js │ ├── MessageIntegrity.js │ ├── ErrorCode.js │ └── address.js ├── util.js ├── client.js ├── attribute.js └── Packet.js ├── config.js ├── package.json ├── LICENSE ├── vs-stun.js └── README.md /lib/attribute/UseCandidate.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | return new Buffer(0); 5 | } 6 | 7 | exports.decode = function decode ( packet, value ) { 8 | return true; 9 | } 10 | 11 | exports.TYPE = 0x0025; 12 | exports.NAME = 'useCanditate'; 13 | -------------------------------------------------------------------------------- /lib/attribute/password.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(value || ''); 5 | 6 | return result; 7 | } 8 | 9 | exports.decode = function decode ( packet, value ) { 10 | return value.toString(); 11 | } 12 | 13 | exports.TYPE = 0x0007; 14 | exports.NAME = 'password'; 15 | -------------------------------------------------------------------------------- /lib/attribute/padding.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(value || ''); 5 | 6 | return result; 7 | } 8 | 9 | exports.decode = function decode ( packet, value ) { 10 | var result = value.toString(); 11 | 12 | return result; 13 | } 14 | 15 | exports.TYPE = 0x0026; 16 | exports.NAME = 'padding'; 17 | -------------------------------------------------------------------------------- /lib/attribute/otherAddress.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var address = require('./address'); 3 | 4 | exports.encode = function encode ( packet, value ) { 5 | return address.encode(packet, value, 'other address'); 6 | } 7 | 8 | exports.decode = function decode ( packet, value ) { 9 | return address.decode(packet, value, 'other address'); 10 | } 11 | 12 | exports.TYPE = 0x802C; 13 | exports.NAME = 'otherAddress'; 14 | -------------------------------------------------------------------------------- /lib/attribute/MappedAddress.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var address = require('./address'); 3 | 4 | exports.encode = function encode ( packet, value ) { 5 | return address.encode(packet, value, 'mapped address'); 6 | } 7 | 8 | exports.decode = function decode ( packet, value ) { 9 | return address.decode(packet, value, 'mapped address'); 10 | } 11 | 12 | exports.TYPE = 0x0001; 13 | exports.NAME = 'mappedAddress'; 14 | -------------------------------------------------------------------------------- /lib/attribute/reflectedFrom.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var address = require('./address'); 3 | 4 | exports.encode = function encode ( packet, value ) { 5 | return address.encode(packet, value, 'reflected from'); 6 | } 7 | 8 | exports.decode = function decode ( packet, value ) { 9 | return address.decode(packet, value, 'reflected from'); 10 | } 11 | 12 | exports.TYPE = 0x000B; 13 | exports.NAME = 'reflectedFrom'; 14 | -------------------------------------------------------------------------------- /lib/attribute/sourceAddress.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var address = require('./address'); 3 | 4 | exports.encode = function encode ( packet, value ) { 5 | return address.encode(packet, value, 'source address'); 6 | } 7 | 8 | exports.decode = function decode ( packet, value ) { 9 | return address.decode(packet, value, 'source address'); 10 | } 11 | 12 | exports.TYPE = 0x0004; 13 | exports.NAME = 'sourceAddress'; 14 | -------------------------------------------------------------------------------- /lib/attribute/changedAddress.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var address = require('./address'); 3 | 4 | exports.encode = function encode ( packet, value ) { 5 | return address.encode(packet, value, 'changed address'); 6 | } 7 | 8 | exports.decode = function decode ( packet, value ) { 9 | return address.decode(packet, value, 'changed address'); 10 | } 11 | 12 | exports.TYPE = 0x0005; 13 | exports.NAME = 'changedAddress'; 14 | -------------------------------------------------------------------------------- /lib/attribute/responseOrigin.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var address = require('./address'); 3 | 4 | exports.encode = function encode ( packet, value ) { 5 | return address.encode(packet, value, 'response origin'); 6 | } 7 | 8 | exports.decode = function decode ( packet, value ) { 9 | return address.decode(packet, value, 'response origin'); 10 | } 11 | 12 | exports.TYPE = 0x802B; 13 | exports.NAME = 'responseOrigin'; 14 | -------------------------------------------------------------------------------- /lib/attribute/AlternateServer.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var address = require('./address'); 3 | 4 | exports.encode = function encode ( packet, value ) { 5 | return address.encode(packet, value, 'alternate server'); 6 | } 7 | 8 | exports.decode = function decode ( packet, value ) { 9 | return address.decode(packet, value, 'alternate server'); 10 | } 11 | 12 | exports.TYPE = 0x8023; 13 | exports.NAME = 'alternateServer'; 14 | -------------------------------------------------------------------------------- /lib/attribute/responseAddress.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var address = require('./address'); 3 | 4 | exports.encode = function encode ( packet, value ) { 5 | return address.encode(packet, value, 'response address'); 6 | } 7 | 8 | exports.decode = function decode ( packet, value ) { 9 | return address.decode(packet, value, 'response address'); 10 | } 11 | 12 | exports.TYPE = 0x0002; 13 | exports.NAME = 'responseAddress'; 14 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | exports.SOFTWARE = 'vs-stun'; 2 | 3 | exports.BLOCKED_UDP = 'Blocked UDP'; 4 | exports.OPEN_INTERNET = 'Open Internet'; 5 | 6 | exports.SYMMETRIC_FIREWALL = 'Symmetric Firewall'; 7 | 8 | exports.FULL_CONE_NAT = 'Full Cone NAT'; 9 | exports.SYMMETRIC_NAT = 'Symmetric NAT'; 10 | exports.RESTRICTED_CONE_NAT = 'Restricted Cone NAT'; 11 | exports.PORT_RESTRICTED_CONE_NAT = 'Port Restricted Cone NAT'; 12 | 13 | exports.RETRANSMISSION_COUNT = 7; 14 | exports.RETRANSMISSION_TIMEOUT = 500; 15 | -------------------------------------------------------------------------------- /lib/attribute/Priority.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(4); 5 | 6 | result.writeUInt32BE(value || 0, 0); 7 | 8 | return result; 9 | } 10 | 11 | exports.decode = function decode ( packet, value ) { 12 | if ( value.length != 4 ) { 13 | return new Error('invalid priority'); 14 | } 15 | 16 | return value.readUInt32BE(0); 17 | } 18 | 19 | exports.TYPE = 0x0024; 20 | exports.NAME = 'priority'; 21 | -------------------------------------------------------------------------------- /lib/attribute/responsePort.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(2); 5 | 6 | result.writeUInt16BE(value || 0, 0); 7 | 8 | return result; 9 | } 10 | 11 | exports.decode = function decode ( packet, value ) { 12 | if ( value.length != 2 ) { 13 | return new Error('invalid response port'); 14 | } 15 | 16 | return value.readUInt16BE(0); 17 | } 18 | 19 | exports.TYPE = 0x0027; 20 | exports.NAME = 'responsePort'; 21 | -------------------------------------------------------------------------------- /lib/attribute/Username.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(value || ''); 5 | 6 | if ( result.length > 512 ) { 7 | return new Error('invalid username'); 8 | } 9 | 10 | return result; 11 | } 12 | 13 | exports.decode = function decode ( packet, value ) { 14 | if ( value.length > 512 ) { 15 | return new Error('invalid username'); 16 | } 17 | 18 | return value.toString(); 19 | } 20 | 21 | exports.TYPE = 0x0006; 22 | exports.NAME = 'username'; 23 | -------------------------------------------------------------------------------- /lib/attribute/IceControlled.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(value || util.generate(8), 'hex'); 5 | 6 | if ( result.length != 8 ) { 7 | return new Error('invalid ice controlled'); 8 | } 9 | 10 | return result; 11 | } 12 | 13 | exports.decode = function decode ( packet, value ) { 14 | if ( value.length != 8 ) { 15 | return new Error('invalid ice controlled'); 16 | } 17 | 18 | return value.toString('hex'); 19 | } 20 | 21 | exports.TYPE = 0x8029; 22 | exports.NAME = 'iceControlled'; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vs-stun", 3 | "version": "0.0.7", 4 | "description": "STUN protocol implementation for NodeJS", 5 | "main": "vs-stun.js", 6 | "scripts": { 7 | "test": "node vs-stun.js test", 8 | "start": "node vs-stun.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/d-vova/vs-stun.git" 13 | }, 14 | "keywords": [ 15 | "stun", 16 | "nat" 17 | ], 18 | "author": "Vladimir Darmin", 19 | "license": "MIT", 20 | "readmeFilename": "README.md", 21 | "gitHead": "406299d3d0b8f545626b358b0d6848a60f68f26d" 22 | } 23 | -------------------------------------------------------------------------------- /lib/attribute/IceControlling.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(value || util.generate(8), 'hex'); 5 | 6 | if ( result.length != 8 ) { 7 | return new Error('invalid ice controlling'); 8 | } 9 | 10 | return result; 11 | } 12 | 13 | exports.decode = function decode ( packet, value ) { 14 | if ( value.length != 8 ) { 15 | return new Error('invalid ice controlling'); 16 | } 17 | 18 | return value.toString('hex'); 19 | } 20 | 21 | exports.TYPE = 0x802a; 22 | exports.NAME = 'iceControlling'; 23 | -------------------------------------------------------------------------------- /lib/attribute/Fingerprint.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var data = new Buffer(packet.raw); 5 | 6 | data.writeUInt16BE(data.length + 8 - 20, 2); 7 | 8 | return util.xor(util.crc32(data), MAGIC); 9 | } 10 | 11 | exports.decode = function decode ( packet, value ) { 12 | if ( value.length != 4 ) { 13 | return new Error('invalid fingerprint'); 14 | } 15 | 16 | return value.toString('hex'); 17 | } 18 | 19 | exports.TYPE = 0x8028; 20 | exports.NAME = 'fingerprint'; 21 | 22 | var MAGIC = new Buffer([ 0x53, 0x54, 0x55, 0x4e ]); 23 | -------------------------------------------------------------------------------- /lib/attribute/Nonce.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(value || '' ); 5 | 6 | if ( value.length >= 128 || result.length >= 764 ) { 7 | return new Error('invalid nonce'); 8 | } 9 | 10 | return result; 11 | } 12 | 13 | exports.decode = function decode ( packet, value ) { 14 | var result = value.toString(); 15 | 16 | if ( value.length >= 764 || result.length >= 128 ) { 17 | return new Error('invalid nonce'); 18 | } 19 | 20 | return result; 21 | } 22 | 23 | exports.TYPE = 0x0015; 24 | exports.NAME = 'nonce'; 25 | -------------------------------------------------------------------------------- /lib/attribute/Realm.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(value || ''); 5 | 6 | if ( value.length >= 128 || result.length >= 764 ) { 7 | return new Error('invalid realm'); 8 | } 9 | 10 | return result; 11 | } 12 | 13 | exports.decode = function decode ( packet, value ) { 14 | var result = value.toString(); 15 | 16 | if ( value.length >= 764 || result.length >= 128 ) { 17 | return new Error('invalid realm'); 18 | } 19 | 20 | return result; 21 | } 22 | 23 | exports.TYPE = 0x0014; 24 | exports.NAME = 'realm'; 25 | -------------------------------------------------------------------------------- /lib/attribute/Software.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(value || ''); 5 | 6 | if ( value.length >= 128 || result.length >= 764 ) { 7 | return new Error('invalid software'); 8 | } 9 | 10 | return result; 11 | } 12 | 13 | exports.decode = function decode ( packet, value ) { 14 | var result = value.toString(); 15 | 16 | if ( value.length >= 764 || result.length >= 128 ) { 17 | return new Error('invalid software'); 18 | } 19 | 20 | return result; 21 | } 22 | 23 | exports.TYPE = 0x8022; 24 | exports.NAME = 'software'; 25 | -------------------------------------------------------------------------------- /lib/attribute/changeRequest.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var result = new Buffer(4); 5 | 6 | var host = value && value.host ? 4 : 0; 7 | var port = value && value.port ? 2 : 0; 8 | 9 | result.writeUInt32BE(host + port, 0); 10 | 11 | return result; 12 | } 13 | 14 | exports.decode = function decode ( packet, value ) { 15 | if ( value.length != 4 ) { 16 | return new Error('invalid change request'); 17 | } 18 | 19 | var flags = value.readUInt32BE(0); 20 | 21 | return { host: !!(flags & 0x4), port: !!(flags & 0x2) } 22 | } 23 | 24 | exports.TYPE = 0x0003; 25 | exports.NAME = 'changeRequest'; 26 | -------------------------------------------------------------------------------- /lib/attribute/UnknownAttributes.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var attributes = value || [ ]; 5 | var result = new Buffer(value.length * 2); 6 | 7 | for ( var i = 0; i < attributes.length; i += 1 ) { 8 | result.writeUInt16BE(attributes[i], i * 2); 9 | } 10 | 11 | return result; 12 | } 13 | 14 | exports.decode = function decode ( packet, value ) { 15 | if ( value.length % 2 != 0 ) { 16 | return new Error('invalid unknown attributes'); 17 | } 18 | 19 | var result = [ ]; 20 | 21 | for ( var i = 0; i < value.length; i += 2 ) { 22 | result.push(value.readUInt16BE(i)); 23 | } 24 | 25 | return result; 26 | } 27 | 28 | exports.TYPE = 0x000a; 29 | exports.NAME = 'unknownAttributes'; 30 | -------------------------------------------------------------------------------- /lib/attribute/XORMappedAddress.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var address = require('./address'); 3 | 4 | exports.encode = function encode ( packet, value ) { 5 | var result = address.encode(packet, value, 'xor mapped address'); 6 | 7 | var key = packet.raw.slice(4, 20); 8 | util.xor(result.slice(2, 4), key).copy(result, 2); 9 | util.xor(result.slice(4), key).copy(result, 4); 10 | 11 | return result; 12 | } 13 | 14 | exports.decode = function decode ( packet, value ) { 15 | var value = new Buffer(value); 16 | 17 | var key = packet.raw.slice(4, 20); 18 | util.xor(value.slice(2, 4), key).copy(value, 2); 19 | util.xor(value.slice(4), key).copy(value, 4); 20 | 21 | return address.decode(packet, value, 'xor mapped address'); 22 | } 23 | 24 | exports.TYPE = 0x0020; 25 | exports.NAME = 'xorMappedAddress'; 26 | -------------------------------------------------------------------------------- /lib/attribute/MessageIntegrity.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | 3 | var util = require('../util'); 4 | 5 | exports.encode = function encode ( packet, value ) { 6 | var username = packet.auth.username; 7 | var password = packet.auth.password; 8 | var realm = packet.doc.attribute.realm; 9 | 10 | realm = realm && realm.obj || ''; 11 | 12 | var key, data = new Buffer(packet.raw); 13 | 14 | data.writeUInt16BE(data.length + 24 - 20, 2); 15 | 16 | if ( realm ) { 17 | var md5 = crypto.createHash('md5'); 18 | 19 | md5.update([ username, realm, password ].join(':')); 20 | 21 | key = md5.digest(); 22 | } 23 | else key = password; 24 | 25 | var hmac = crypto.createHmac('sha1', key); 26 | 27 | hmac.update(data); 28 | 29 | return new Buffer(hmac.digest()); 30 | } 31 | 32 | exports.decode = function decode ( packet, value ) { 33 | if ( value.length != 20 ) { 34 | return new Error('invalid message integrity'); 35 | } 36 | 37 | return value.toString('hex'); 38 | } 39 | 40 | exports.TYPE = 0x0008; 41 | exports.NAME = 'messageIntegrity'; 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Vladimir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/attribute/ErrorCode.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value ) { 4 | var value = value || { } 5 | 6 | if ( !value.reason ) { 7 | switch ( value.code ) { 8 | case 300: value.reason = 'Try Alternate'; break; 9 | case 400: value.reason = 'Bad Request'; break; 10 | case 401: value.reason = 'Unauthorized'; break; 11 | case 420: value.reason = 'Unknown Attribute'; break; 12 | case 438: value.reason = 'Stale Nonce'; break; 13 | case 500: value.reason = 'Server Error'; break; 14 | default: value.reason = ''; 15 | } 16 | } 17 | 18 | if ( !value.code ) { 19 | return new Error('invalid error code'); 20 | } 21 | 22 | if ( value.reason.length >= 128 ) { 23 | return new Error('invalid error code'); 24 | } 25 | 26 | if ( value.code < 300 || value.code >= 700 ) { 27 | return new Error('invalid error code'); 28 | } 29 | 30 | var result = new Buffer(4 + value.reason.length); 31 | 32 | result.writeUInt16BE(0, 0); 33 | result.writeUInt8(value.code / 100 | 0, 2); 34 | result.writeUInt8(value.code % 100, 3); 35 | result.write(value.reason, 4); 36 | 37 | return result; 38 | } 39 | 40 | exports.decode = function decode ( packet, value ) { 41 | var result = { 42 | code: value.readUInt8(2) * 100 + value.readUInt8(3) % 100, 43 | reason: value.toString('utf8', 4) 44 | } 45 | 46 | if ( result.reason.length >= 128 ) { 47 | return new Error('invalid error code'); 48 | } 49 | 50 | if ( result.code < 300 || result.code >= 700 ) { 51 | return new Error('invalid error code'); 52 | } 53 | 54 | return result; 55 | } 56 | 57 | exports.TYPE = 0x0009; 58 | exports.NAME = 'errorCode'; 59 | -------------------------------------------------------------------------------- /lib/attribute/address.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | 3 | exports.encode = function encode ( packet, value, name ) { 4 | var result, value = util.normalize.address(value); 5 | 6 | if ( !value ) return new Error('invalid ' + name); 7 | 8 | switch ( value.family ) { 9 | case util.IPv4: { 10 | result = new Buffer(8); 11 | result.writeUInt8(0x01, 1); 12 | 13 | if ( value.host.length != 4 ) { 14 | return new Error('invalid host of ' + name); 15 | } 16 | 17 | for ( var i = 0; i < 4; i += 1 ) { 18 | result.writeUInt8(value.host[i], 4 + i); 19 | } 20 | } break; 21 | case util.IPv6: { 22 | result = new Buffer(20); 23 | result.writeUInt8(0x02, 1); 24 | 25 | if ( value.host.length != 8 ) { 26 | return new Error('invalid host of ' + name); 27 | } 28 | 29 | for ( var i = 0; i < 8; i += 1 ) { 30 | result.writeUInt16BE(value.host[i], 4 + i * 2); 31 | } 32 | } break; 33 | } 34 | 35 | result.writeUInt8(0, 0); 36 | result.writeUInt16BE(value.port, 2); 37 | 38 | return result; 39 | } 40 | 41 | exports.decode = function decode ( packet, value, name ) { 42 | var result = { host: [ ], port: value.readUInt16BE(2) } 43 | 44 | switch ( value.readUInt8(1) ) { 45 | case 0x01: { 46 | if ( value.length != 8 ) return new Error('invalid ' + name); 47 | 48 | result.family = util.IPv4; 49 | 50 | for ( var i = 0; i < 4; i += 1 ) { 51 | result.host.push(value.readUInt8(4 + i)); 52 | } 53 | 54 | result.host = result.host.join('.'); 55 | } break; 56 | case 0x02: { 57 | if ( value.length != 20 ) return new Error('invalid ' + name); 58 | 59 | result.family = util.IPv6; 60 | 61 | for ( var i = 0; i < 8; i += 1 ) { 62 | result.host.push(value.toString('hex', 4 + i * 2, 6 + i * 2)); 63 | } 64 | 65 | result.host = result.host.join(':'); 66 | } break; 67 | default: return new Error('invalid ' + name); 68 | } 69 | 70 | return result; 71 | } 72 | -------------------------------------------------------------------------------- /vs-stun.js: -------------------------------------------------------------------------------- 1 | var dgram = require('dgram'); 2 | 3 | var util = require('./lib/util'); 4 | var client = require('./lib/client'); 5 | var Packet = require('./lib/Packet'); 6 | 7 | var parse = exports.parse = function parse ( data ) { 8 | return Packet.parse(data); 9 | } 10 | 11 | var create = exports.create = function create ( auth ) { 12 | return new Packet(auth); 13 | } 14 | 15 | create.bindingRequest = function createBindingRequest ( auth ) { 16 | var packet = new Packet(auth); 17 | 18 | packet.type = Packet.BINDING_REQUEST; 19 | 20 | return packet; 21 | } 22 | 23 | create.bindingSuccess = function createBindingSuccess ( auth ) { 24 | var packet = new Packet(auth); 25 | 26 | packet.type = Packet.BINDING_SUCCESS; 27 | 28 | return packet; 29 | } 30 | 31 | create.bindingFailure = function createBindingFailure ( auth ) { 32 | var packet = new Packet(auth); 33 | 34 | packet.type = Packet.BINDING_FAILURE; 35 | 36 | return packet; 37 | } 38 | 39 | create.sharedSecretRequest = function createShareSecretRequest ( auth ) { 40 | var packet = new Packet(auth); 41 | 42 | packet.type = Packet.SSECRET_REQUEST; 43 | 44 | return packet; 45 | } 46 | 47 | create.sharedSecretSuccess = function createSharedSecretSuccess ( auth ) { 48 | var packet = new Packet(auth); 49 | 50 | packet.type = Packet.SSECRET_SUCCESS; 51 | 52 | return packet; 53 | } 54 | 55 | create.sharedSecretFailure = function createSharedSecretFailure ( auth ) { 56 | var packet = new Packet(auth); 57 | 58 | packet.type = Packet.SSECRET_FAILURE; 59 | 60 | return packet; 61 | } 62 | 63 | 64 | var check = exports.check = function check ( data ) { 65 | return Packet.parse.check(data); 66 | } 67 | 68 | 69 | var connect = exports.connect = function connect ( server, callback, retransmission ) { 70 | var server = server || { } 71 | var socket = dgram.createSocket('udp4'); 72 | 73 | var done = function done ( error, value ) { 74 | if ( error ) { 75 | socket.close(); 76 | 77 | callback(error); 78 | } 79 | else { 80 | socket.stun = value; 81 | 82 | callback(null, socket); 83 | } 84 | } 85 | 86 | socket.on('listening', function ( ) { 87 | client.resolve(socket, server, done, retransmission); 88 | }); 89 | 90 | socket.bind(); 91 | } 92 | 93 | 94 | var resolve = exports.resolve = function resolve ( socket, server, callback, retransmission ) { 95 | client.resolve(socket, server, callback, retransmission); 96 | } 97 | 98 | var respond = exports.respond = function respond ( socket, data, callback ) { 99 | } 100 | 101 | 102 | if ( require.main === module && process.argv[2] == 'test' ) { 103 | var exec = require('child_process').exec; 104 | 105 | var log = function log ( error, value ) { 106 | console.log(error || value); 107 | } 108 | 109 | exec('node lib/Packet.js', log); 110 | 111 | var socket, server = { host: 'stun.l.google.com', port: 19302 } 112 | 113 | var callback = function callback ( error, value ) { 114 | if ( !error ) { 115 | socket = value; 116 | 117 | console.log(socket.stun); 118 | 119 | socket.close(); 120 | } 121 | else console.log('Something went wrong: ' + error); 122 | } 123 | 124 | connect(server, callback); 125 | } 126 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var HEX = '0123456789abcdef'; 2 | 3 | var IPv4 = exports.IPv4 = 'IPv4'; 4 | var IPv6 = exports.IPv6 = 'IPv6'; 5 | 6 | 7 | var parse = exports.parse = { } 8 | 9 | parse.host = function parseHost ( value ) { 10 | var result = [ ], value = String(value); 11 | 12 | if ( value.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) ) { 13 | var parts = value.split('.'); 14 | 15 | for ( var i = 0; i < parts.length; i += 1 ) { 16 | result.push(parseInt(parts[i], 10)); 17 | } 18 | } 19 | 20 | if ( value.match(/^(?:[0-9a-f]{4}:){7}[0-9a-f]{4}$/) ) { 21 | var parts = value.split(':'); 22 | 23 | for ( var i = 0; i < parts.length; i += 1 ) { 24 | result.push(parseInt(parts[i], 16)); 25 | } 26 | } 27 | 28 | return result; 29 | } 30 | 31 | parse.family = function parseFamily ( value ) { 32 | var value = String(value); 33 | 34 | if ( value.match(/^.*4$/) ) return IPv4; 35 | if ( value.match(/^.*6$/) ) return IPv6; 36 | 37 | return null; 38 | } 39 | 40 | 41 | var normalize = exports.normalize = { } 42 | 43 | normalize.address = function normalizeAddress ( value ) { 44 | var result = { } 45 | 46 | result.family = parse.family(value && value.family); 47 | result.host = parse.host(value && value.host); 48 | result.port = parseInt(value && value.port); 49 | 50 | if ( value && !value.family && result.host ) { 51 | if ( result.host.length == 4 ) result.family = IPv4; 52 | if ( result.host.length == 8 ) result.family = IPv6; 53 | } 54 | 55 | if ( result.port != result.port & 0xFFFF ) { 56 | result.port = 0; 57 | } 58 | 59 | return result.family && result.host && result.port && result; 60 | } 61 | 62 | 63 | var generate = exports.generate = function generate ( length ) { 64 | var length = 2 * (length || 0), token = ''; 65 | 66 | for ( var i = 0; i < length; i += 1 ) { 67 | token += HEX.charAt(Math.random() * 16 | 0); 68 | } 69 | 70 | return token; 71 | } 72 | 73 | var saslPrep = exports.saslPrep = function saslPrep ( string ) { 74 | var result = ''; 75 | 76 | result = string; 77 | 78 | return result; 79 | } 80 | 81 | var xor = exports.xor = function xor ( data, pattern ) { 82 | var result = new Buffer(data.length); 83 | 84 | for ( var i = 0; i < data.length; i += 1 ) { 85 | result[i] = data[i] ^ pattern[i % pattern.length]; 86 | } 87 | 88 | return result; 89 | } 90 | 91 | var crc32 = exports.crc32 = function crc32 ( data ) { 92 | crc = 0xffffffff; 93 | 94 | for ( var i = 0; i < data.length; i += 1 ) { 95 | var key = (crc & 0xff) ^ data[i]; 96 | 97 | crc = (crc >>> 8) ^ crc32.table[key]; 98 | } 99 | 100 | crc = crc ^ 0xffffffff; 101 | 102 | var buffer = new Buffer(4); 103 | 104 | buffer.writeInt32BE(crc, 0); 105 | 106 | return buffer; 107 | } 108 | 109 | crc32.table = [ 110 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 111 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 112 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 113 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 114 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 115 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 116 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 117 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 118 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 119 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 120 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 121 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 122 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 123 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 124 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 125 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 126 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 127 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 128 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 129 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 130 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 131 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 132 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 133 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 134 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 135 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 136 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 137 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 138 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 139 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 140 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 141 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 142 | ]; 143 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | var Packet = require('./Packet'); 2 | var config = require('../config'); 3 | 4 | var resolve = exports.resolve = function resolve ( socket, server, callback, retransmission ) { 5 | var address = socket.address(); 6 | var retransmission = retransmission || { } 7 | 8 | var local = { host: address.address, port: address.port }; 9 | var count = retransmission.count; 10 | var timeout = retransmission.timeout; 11 | 12 | test.udp(socket, server, count, timeout, function ( error, value ) { 13 | if ( error ) return callback(null, label({ local: local })); 14 | 15 | var other = value.other; 16 | var result = { local: local, public: value.mapped } 17 | 18 | if ( !other ) return callback(null, label(result)); 19 | 20 | if ( !test.compare(result.public, result.local) ) { 21 | var otser = { host: other.host, port: server.port } 22 | 23 | test.mapping(socket, otser, count, timeout, function ( error, value ) { 24 | if ( error ) return callback(error); 25 | 26 | if ( !test.compare(value.mapped, result.public) ) { 27 | test.portMapping(socket, other, count, timeout, function ( error, value ) { 28 | if ( error ) return callback(error); 29 | 30 | result.mapping = { host: true, port: !test.compare(value.mapped, result.public) } 31 | 32 | if ( result.filtering != null ) callback(null, label(result)); 33 | }); 34 | } 35 | else { 36 | result.mapping = { host: false, port: false } 37 | 38 | if ( result.filtering != null ) callback(null, label(result)); 39 | } 40 | }); 41 | } 42 | else result.mapping = { host: false, port: false } 43 | 44 | test.filtering(socket, server, count, timeout, function ( error, value ) { 45 | if ( error ) return callback(error); 46 | 47 | if ( value ) { 48 | test.portFiltering(socket, server, count, timeout, function ( error, value ) { 49 | if ( error ) return callback(error); 50 | 51 | result.filtering = { host: true, port: !!value } 52 | 53 | if ( result.mapping != null ) callback(null, label(result)); 54 | }); 55 | } 56 | else { 57 | result.filtering = { host: false, port: false } 58 | 59 | if ( result.mapping != null ) callback(null, label(result)); 60 | } 61 | }); 62 | }); 63 | } 64 | 65 | 66 | var label = function label ( result ) { 67 | if ( !result.public ) result.type = config.BLOCKED_UDP; 68 | else { 69 | var mapping = result.mapping || { } 70 | var filtering = result.filtering || { } 71 | 72 | if ( test.compare(result.public, result.local) ) { 73 | if ( filtering.host || filtering.port ) { 74 | result.label = config.SYMMETRIC_FIREWALL; 75 | } 76 | else result.type = config.OPEN_INTERNET; 77 | } 78 | else { 79 | if ( mapping.host || mapping.port ) { 80 | result.type = config.SYMMETRIC_NAT; 81 | } 82 | else { 83 | if ( !filtering.host ) { 84 | result.type = config.FULL_CONE_NAT; 85 | } 86 | else if ( !filtering.port ) { 87 | result.type = config.RESTRICTED_CONE_NAT; 88 | } 89 | else result.type = config.PORT_RESTRICTED_CONE_NAT; 90 | } 91 | } 92 | } 93 | 94 | return result; 95 | } 96 | 97 | 98 | var test = function test ( ) { } 99 | 100 | test.compare = function compare ( serverA, serverB ) { 101 | var serverA = serverA || { } 102 | var serverB = serverB || { } 103 | 104 | return serverA.host == serverB.host && serverA.port == serverB.port; 105 | } 106 | 107 | test.udp = function testUDP ( socket, server, count, timeout, callback ) { 108 | var done = function done ( error, value ) { 109 | if ( error ) return callback(error); 110 | 111 | var attribute = value.packet.doc.attribute; 112 | 113 | var other = attribute.otherAddress || attribute.changedAddress; 114 | var mapped = attribute.xorMappedAddress || attribute.mappedAddress; 115 | 116 | callback(null, { other: other, mapped: mapped ? mapped.value.obj : { } }); 117 | } 118 | 119 | var packet = new Packet(); 120 | 121 | packet.type = Packet.BINDING_REQUEST; 122 | 123 | transmit(socket, server, packet, count, timeout, done); 124 | } 125 | 126 | 127 | test.mapping = function testMapping ( socket, server, count, timeout, callback ) { 128 | var done = function done ( error, value ) { 129 | if ( error ) return callback(error); 130 | 131 | var attribute = value.packet.doc.attribute; 132 | 133 | var mapped = attribute.xorMappedAddress || attribute.mappedAddress; 134 | 135 | callback(null, { mapped: mapped ? mapped.value.obj : { } }); 136 | } 137 | 138 | var packet = new Packet(); 139 | 140 | packet.type = Packet.BINDING_REQUEST; 141 | 142 | transmit(socket, server, packet, count, timeout, done); 143 | } 144 | 145 | test.portMapping = function testPortMapping ( socket, server, count, timeout, callback ) { 146 | var done = function done ( error, value ) { 147 | if ( error ) return callback(error); 148 | 149 | var attribute = value.packet.doc.attribute; 150 | 151 | var mapped = attribute.xorMappedAddress || attribute.mappedAddress; 152 | 153 | callback(null, { mapped: mapped ? mapped.value.obj : { } }); 154 | } 155 | 156 | var packet = new Packet(); 157 | 158 | packet.type = Packet.BINDING_REQUEST; 159 | 160 | transmit(socket, server, packet, count, timeout, done); 161 | } 162 | 163 | 164 | test.filtering = function testFiltering ( socket, server, count, timeout, callback ) { 165 | var done = function done ( error, value ) { 166 | callback(null, !!error); 167 | } 168 | 169 | var packet = new Packet(); 170 | 171 | packet.type = Packet.BINDING_REQUEST; 172 | packet.append.changeRequest({ host: true, port: true }); 173 | 174 | transmit(socket, server, packet, count, timeout, done); 175 | } 176 | 177 | test.portFiltering = function testPortFiltering ( socket, server, count, timeout, callback ) { 178 | var done = function done ( error, value ) { 179 | callback(null, !!error); 180 | } 181 | 182 | var packet = new Packet(); 183 | 184 | packet.type = Packet.BINDING_REQUEST; 185 | packet.append.changeRequest({ host: false, port: true }); 186 | 187 | transmit(socket, server, packet, count, timeout, done); 188 | } 189 | 190 | 191 | var transmit = function transmit ( socket, server, packet, count, timeout, callback ) { 192 | var count = count || config.RETRANSMISSION_COUNT; 193 | var timeout = timeout || config.RETRANSMISSION_TIMEOUT; 194 | var timer, error, host = server.host, port = server.port; 195 | 196 | var done = function done ( error, value ) { 197 | socket.removeListener('message', onValue); 198 | socket.removeListener('error', onError); 199 | 200 | callback(error, value); 201 | } 202 | 203 | var retry = function retry ( ) { 204 | if ( count > 1 ) { 205 | count -= 1; timeout *= 2; attempt(); 206 | } 207 | else done(error || new Error('request timeout')); 208 | } 209 | 210 | var onValue = function onValue ( data, info ) { 211 | clearTimeout(timer); 212 | 213 | var packet = Packet.parse(data); 214 | 215 | if ( packet instanceof Error ) done(packet); 216 | else { 217 | var result = { 218 | packet: Packet.parse(data), 219 | address: { host: info.address, port: info.port } 220 | } 221 | 222 | done(null, result); 223 | } 224 | } 225 | 226 | var onError = function onError ( value ) { 227 | error = value; 228 | } 229 | 230 | var attempt = function attempt ( ) { 231 | socket.send(packet.raw, 0, packet.raw.length, port, host); 232 | 233 | timer = setTimeout(retry, timeout); 234 | } 235 | 236 | socket.on('message', onValue); 237 | socket.on('error', onError); 238 | 239 | attempt(); 240 | } 241 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vs-stun 2 | ======= 3 | 4 | STUN protocol implementation in NodeJS 5 | 6 | 7 | Installation 8 | ------------ 9 | 10 | ``` 11 | npm install vs-stun 12 | ``` 13 | 14 | 15 | Quick Start 16 | ----------- 17 | 18 | Create a datagram socket, discover its host, port, and topology: 19 | 20 | ```javascript 21 | var stun = require('vs-stun'); 22 | 23 | var socket, server = { host: 'stun.l.google.com', port: 19302 } 24 | 25 | var callback = function callback ( error, value ) { 26 | if ( !error ) { 27 | socket = value; 28 | 29 | console.log(socket.stun); 30 | 31 | socket.close(); 32 | } 33 | else console.log('Something went wrong: ' + error); 34 | } 35 | 36 | stun.connect(server, callback); 37 | ``` 38 | 39 | Or discover host, port, and topology of an existing socket: 40 | 41 | ```javascript 42 | var stun = require('vs-stun'); 43 | 44 | // socket is created and opened here... 45 | 46 | var server = { host: 'stun.l.google.com', port: 19302 } 47 | 48 | var callback = function callback ( error, value ) { 49 | if ( !error ) { 50 | console.log(value); 51 | 52 | socket.close(); 53 | } 54 | else console.log('Something went wrong: ' + error); 55 | } 56 | 57 | stun.resolve(socket, server, callback); 58 | ``` 59 | 60 | 61 | Create Packet 62 | ------------- 63 | 64 | ```javascript 65 | var packet = stun.create({ username: 'name', password: 'pswd' }); 66 | ``` 67 | 68 | 69 | ##### Binding Request ##### 70 | 71 | ```javascript 72 | var packet = stun.create.bindingRequest({ username: 'name', password: 'pswd' }); 73 | ``` 74 | 75 | ##### Binding Response ##### 76 | 77 | ```javascript 78 | var packet = stun.create.bindingSuccess({ username: 'name', password: 'pswd' }); 79 | ``` 80 | 81 | ##### Binding Error ##### 82 | 83 | ```javascript 84 | var packet = stun.create.bindingFailure({ username: 'name', password: 'pswd' }); 85 | ``` 86 | 87 | ##### Shared Secret Request ##### 88 | 89 | ```javascript 90 | var packet = stun.create.sharedSecretRequest({ username: 'name', password: 'pswd' }); 91 | ``` 92 | 93 | ##### Shared Secret Response ##### 94 | 95 | ```javascript 96 | var packet = stun.create.sharedSecretSuccess({ username: 'name', password: 'pswd' }); 97 | ``` 98 | 99 | ##### Shared Secret Error ##### 100 | 101 | ```javascript 102 | var packet = stun.create.sharedSecretFailure({ username: 'name', password: 'pswd' }); 103 | ``` 104 | 105 | 106 | Append Attributes 107 | ----------------- 108 | 109 | ### RFC 3489 (STUN) ### 110 | 111 | ##### Response-Address ##### 112 | 113 | ```javascript 114 | var error, address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 115 | 116 | if ( error = packet.append.responseAddress(address) ) console.log(error); 117 | ``` 118 | 119 | ##### Changed-Address ##### 120 | 121 | ```javascript 122 | var error, address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 123 | 124 | if ( error = packet.append.changedAddress(address) ) console.log(error); 125 | ``` 126 | 127 | ##### Source-Address ##### 128 | 129 | ```javascript 130 | var error, address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 131 | 132 | if ( error = packet.append.sourceAddress(address) ) console.log(error); 133 | ``` 134 | 135 | ##### Password ##### 136 | 137 | ```javascript 138 | var error, password = "secret"; 139 | 140 | if ( error = packet.append.password(password) ) console.log(error); 141 | ``` 142 | 143 | ##### Reflected-From ##### 144 | 145 | ```javascript 146 | var error, address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 147 | 148 | if ( error = packet.append.reflectedFrom(address) ) console.log(error); 149 | ``` 150 | 151 | 152 | ### RFC 5245 (ICE) ### 153 | 154 | ##### Priority ##### 155 | 156 | ```javascript 157 | var error, level = 12345; 158 | 159 | if ( error = packet.append.priority(level) ) console.log(error); 160 | ``` 161 | 162 | ##### Use-Candidate ##### 163 | 164 | ```javascript 165 | var error = null; 166 | 167 | if ( error = packet.append.useCandidate() ) console.log(error); 168 | ``` 169 | 170 | ##### Ice-Controlled ##### 171 | 172 | ```javascript 173 | var error, tieBreaker = '08192a3b4c5e6d7f'; 174 | 175 | if ( error = packet.append.iceControlled(tieBreaker) ) console.log(error); 176 | ``` 177 | 178 | ##### Ice-Controlling ##### 179 | 180 | ```javascript 181 | var error, tieBreaker = '08192a3b4c5e6d7f'; 182 | 183 | if ( error = packet.append.iceControlling(tieBreaker) ) console.log(error); 184 | ``` 185 | 186 | 187 | ### RFC 5389 (STUN) ### 188 | 189 | ##### Mapped-Address ##### 190 | 191 | ```javascript 192 | var error, address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 193 | 194 | if ( error = packet.append.mappedAddress(address) ) console.log(error); 195 | ``` 196 | 197 | ##### XOR-Mapped-Address ##### 198 | 199 | ```javascript 200 | var error, address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 201 | 202 | if ( error = packet.append.xorMappedAddress(address) ) console.log(error); 203 | ``` 204 | 205 | ##### Username ##### 206 | 207 | ```javascript 208 | var error, name = 'Joe'; 209 | 210 | if ( error = packet.append.username(name) ) console.log(error); 211 | ``` 212 | 213 | ##### Message-Integrity ##### 214 | 215 | ```javascript 216 | var error = null; 217 | 218 | if ( error = packet.append.messageIntegrity() ) console.log(error); 219 | ``` 220 | 221 | ##### Fingerprint ##### 222 | 223 | ```javascript 224 | var error = null; 225 | 226 | if ( error = packet.append.fingerprint() ) console.log(error); 227 | ``` 228 | 229 | ##### Error-Code ##### 230 | 231 | ```javascript 232 | var error, errorCode = { code: 300, reason: 'Try Alternate' } 233 | 234 | if ( error = packet.append.errorCode(errorCode) ) console.log(error); 235 | ``` 236 | 237 | ##### Realm ##### 238 | 239 | ```javascript 240 | var error, name = 'realm'; 241 | 242 | if ( error = packet.append.realm(name) ) console.log(error); 243 | ``` 244 | 245 | ##### Nonce ##### 246 | 247 | ```javascript 248 | var error, name = 'nonce'; 249 | 250 | if ( error = packet.append.nonce(name) ) console.log(error); 251 | ``` 252 | 253 | ##### Unknown-Attributes ##### 254 | 255 | ```javascript 256 | var error, attributes = [ 0x02, 0x04, 0x05 ]; 257 | 258 | if ( error = packet.append.unknownAttributes(attributes) ) console.log(error); 259 | ``` 260 | 261 | ##### Software ##### 262 | 263 | ```javascript 264 | var error, name = 'soft'; 265 | 266 | if ( error = packet.append.software('soft') ) console.log(error); 267 | ``` 268 | 269 | ##### Alternate-Server ##### 270 | 271 | ```javascript 272 | var error, address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 273 | 274 | if ( error = packet.append.alternateServer(address) ) console.log(error); 275 | ``` 276 | 277 | 278 | ### RFC 5780 (NAT) ### 279 | 280 | ##### Change-Request ##### 281 | 282 | ```javascript 283 | var error, flags = { host: true, port: true } 284 | 285 | if ( error = packet.append.changeRequest(flags) ) console.log(error); 286 | ``` 287 | 288 | ##### Response-Origin ##### 289 | 290 | ```javascript 291 | var error, address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 292 | 293 | if ( error = packet.append.responseOrigin(address) ) console.log(error); 294 | ``` 295 | 296 | ##### Other-Address ##### 297 | 298 | ```javascript 299 | var error, address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 300 | 301 | if ( error = packet.append.otherAddress(address) ) console.log(error); 302 | ``` 303 | 304 | ##### Response-Port ##### 305 | 306 | ```javascript 307 | var error, port = 8080; 308 | 309 | if ( error = packet.append.responsePort(port) ) console.log(error); 310 | ``` 311 | 312 | ##### Padding ##### 313 | 314 | ```javascript 315 | var error, padding = 'some string for padding....'; 316 | 317 | if ( error = packet.append.padding(padding) ) console.log(error); 318 | ``` 319 | 320 | 321 | Error Codes and Reasons 322 | ----------------------- 323 | 324 | ### RFC 5389 (STUN) ### 325 | 326 | - `300` - Try Alternate 327 | - `400` - Bad Request 328 | - `401` - Unauthorized 329 | - `420` - Unknown Attribute 330 | - `438` - Stale Nonce 331 | - `500` - Server Error 332 | 333 | 334 | License 335 | ------- 336 | 337 | MIT 338 | -------------------------------------------------------------------------------- /lib/attribute.js: -------------------------------------------------------------------------------- 1 | var ATTRIBUTE = { 2 | alternateServer : require('./Attribute/alternateServer'), 3 | changeRequest : require('./Attribute/changeRequest'), 4 | changedAddress : require('./Attribute/changedAddress'), 5 | errorCode : require('./Attribute/errorCode'), 6 | fingerprint : require('./Attribute/fingerprint'), 7 | iceControlled : require('./Attribute/iceControlled'), 8 | iceControlling : require('./Attribute/iceControlling'), 9 | mappedAddress : require('./Attribute/mappedAddress'), 10 | messageIntegrity : require('./Attribute/messageIntegrity'), 11 | nonce : require('./Attribute/nonce'), 12 | otherAddress : require('./Attribute/otherAddress'), 13 | padding : require('./Attribute/padding'), 14 | password : require('./Attribute/password'), 15 | priority : require('./Attribute/priority'), 16 | realm : require('./Attribute/realm'), 17 | reflectedFrom : require('./Attribute/reflectedFrom'), 18 | responseAddress : require('./Attribute/responseAddress'), 19 | responseOrigin : require('./Attribute/responseOrigin'), 20 | responsePort : require('./Attribute/responsePort'), 21 | software : require('./Attribute/software'), 22 | sourceAddress : require('./Attribute/sourceAddress'), 23 | unknownAttributes : require('./Attribute/unknownAttributes'), 24 | useCandidate : require('./Attribute/useCandidate'), 25 | username : require('./Attribute/username'), 26 | xorMappedAddress : require('./Attribute/xorMappedAddress') 27 | } 28 | 29 | var TYPE = { } 30 | 31 | for ( var name in ATTRIBUTE ) TYPE[ATTRIBUTE[name].TYPE] = ATTRIBUTE[name]; 32 | 33 | 34 | var Attribute = module.exports = function Attribute ( name, obj, raw, pattern ) { 35 | var attribute = ATTRIBUTE[name] || new Attribute.Unknown(name); 36 | var value = raw || new Buffer(0); 37 | var type = new Buffer(2), length = new Buffer(2); 38 | var pattern = new Buffer(pattern || '00', 'hex'); 39 | var padding = new Buffer((4 - value.length % 4) % 4); 40 | 41 | type.writeUInt16BE(attribute.TYPE, 0); 42 | length.writeUInt16BE(value.length, 0); 43 | 44 | for ( var i = 0; i < padding.length; i += 1 ) { 45 | padding[i] = pattern[i % pattern.length]; 46 | } 47 | 48 | this.name = name; 49 | this.raw = Buffer.concat([ type, length, value, padding ]); 50 | 51 | this.type = { obj: attribute.TYPE, raw: type } 52 | this.length = { obj: value.length, raw: length } 53 | this.value = { obj: obj, raw: value } 54 | this.padding = { obj: padding.toString('hex'), raw: padding } 55 | } 56 | 57 | Attribute.prototype.toString = function toString ( ) { 58 | var string = [ 59 | Attribute.typeToString(this), 60 | '=>', 61 | Attribute.paddingToString(this), 62 | '-', 63 | Attribute.lengthToString(this), 64 | '-', 65 | Attribute.valueToString(this) 66 | ]; 67 | 68 | return string.join(' '); 69 | } 70 | 71 | 72 | Attribute.Unknown = function UnknownAttribute ( type ) { 73 | this.TYPE = parseInt(type); 74 | this.NAME = 'unknownAttribute'; 75 | } 76 | 77 | Attribute.Unknown.prototype.encode = function encode ( packet, value ) { 78 | return new Buffer(value || '', 'hex'); 79 | } 80 | 81 | Attribute.Unknown.prototype.decode = function decode ( packet, value ) { 82 | return value.toString('hex'); 83 | } 84 | 85 | 86 | Attribute.expand = function expand ( packet ) { 87 | for ( var name in ATTRIBUTE ) { 88 | Attribute.expand.attribute(packet, name); 89 | } 90 | } 91 | 92 | Attribute.expand.attribute = function expandAttribute ( packet, name ) { 93 | packet.append[name] = function append ( obj, pattern ) { 94 | var raw = ATTRIBUTE[name].encode(packet, obj); 95 | if ( raw instanceof Error ) return raw; 96 | 97 | var obj = ATTRIBUTE[name].decode(packet, raw); 98 | if ( obj instanceof Error ) return obj; 99 | 100 | var pattern = new Buffer(pattern || '00', 'hex'); 101 | var attribute = new Attribute(name, obj, raw, pattern); 102 | 103 | return packet.append(attribute); 104 | } 105 | } 106 | 107 | //Attribute.expand.check = function expandCheck ( packet, name ) { 108 | // if ( packet.doc.attribute.fingerprint ) { 109 | // return new Error('fingerprint was the last attribute'); 110 | // } 111 | // 112 | // if ( packet.doc.attribute.messageIntegrity ) { 113 | // if ( name != ATTRIBUTE.fingerprint.NAME ) { 114 | // return new Error('only fingerprint can follow message integrity'); 115 | // } 116 | // } 117 | // 118 | // switch ( name ) { 119 | // case ATTRIBUTE.changeRequest.NAME: { 120 | // if ( !(packet.doc.type.obj & 0x0001) ) { 121 | // return new Error('change request requires a binding request packet type'); 122 | // } 123 | // } break; 124 | // case ATTRIBUTE.errorCode.NAME: { 125 | // if ( !(packet.doc.type.obj & 0x0010) ) { 126 | // return new Error('error code requires an error response packet type'); 127 | // } 128 | // } break; 129 | // case ATTRIBUTE.iceControlled.NAME: { 130 | // if ( packet.doc.attribute.iceControlling ) { 131 | // return new Error('ice controlling is already present'); 132 | // } 133 | // } break; 134 | // case ATTRIBUTE.iceControlling.NAME: { 135 | // if ( packet.doc.attribute.iceControlled ) { 136 | // return new Error('ice controlled is already present'); 137 | // } 138 | // } break; 139 | // case ATTRIBUTE.unknownAttributes.NAME: { 140 | // if ( !packet.doc.attribute.errorCode ) { 141 | // return new Error('error code must be present'); 142 | // } 143 | // else if ( packet.doc.attribute.errorCode.obj != 420 ) { 144 | // return new Error('unknown attributes require 420 error code'); 145 | // } 146 | // } break; 147 | // } 148 | //} 149 | 150 | Attribute.parse = function parse ( packet, data ) { 151 | if ( !Attribute.parse.check(packet, data) ) { 152 | return new Error('invalid attribute'); 153 | } 154 | 155 | var error, offset = packet.raw.length; 156 | 157 | var type = data.readUInt16BE(offset); 158 | var length = data.readUInt16BE(offset + 2); 159 | 160 | var begin = offset + 4 + length; 161 | var end = begin + (4 - length % 4) % 4; 162 | 163 | var name = TYPE[type] ? TYPE[type].NAME : type; 164 | var raw = data.slice(offset + 4, offset + 4 + length); 165 | var obj = ATTRIBUTE[name].decode(packet, raw); 166 | var pattern = data.toString('hex', begin, end); 167 | 168 | if ( obj instanceof Error ) return obj; 169 | 170 | return new Attribute(name, obj, raw, pattern); 171 | } 172 | 173 | Attribute.parse.check = function parseCheck ( packet, data ) { 174 | var offset = packet.raw.length; 175 | 176 | if ( data.length - offset < 4 ) return false; 177 | if ( (data.length - offset) % 4 != 0 ) return false; 178 | 179 | var length = data.readUInt16BE(offset + 2); 180 | 181 | return offset + 4 + length + (4 - length % 4) % 4 <= data.length; 182 | } 183 | 184 | 185 | Attribute.typeToString = function typeToString ( attribute ) { 186 | var type = '(0x' + attribute.type.raw.toString('hex') + ') '; 187 | 188 | switch ( attribute.type.obj ) { 189 | case ATTRIBUTE.alternateServer.TYPE : return type + 'ALTERNATE SERVER '; 190 | case ATTRIBUTE.changeRequest.TYPE : return type + 'CHANGE REQUEST '; 191 | case ATTRIBUTE.changedAddress.TYPE : return type + 'CHANGED ADDRESS '; 192 | case ATTRIBUTE.errorCode.TYPE : return type + 'ERROR CODE '; 193 | case ATTRIBUTE.fingerprint.TYPE : return type + 'FINGERPRINT '; 194 | case ATTRIBUTE.iceControlled.TYPE : return type + 'ICE CONTROLLED '; 195 | case ATTRIBUTE.iceControlling.TYPE : return type + 'ICE CONTROLLING '; 196 | case ATTRIBUTE.mappedAddress.TYPE : return type + 'MAPPED ADDRESS '; 197 | case ATTRIBUTE.messageIntegrity.TYPE : return type + 'MESSAGE INTEGRITY '; 198 | case ATTRIBUTE.nonce.TYPE : return type + 'NONCE '; 199 | case ATTRIBUTE.otherAddress.TYPE : return type + 'OTHER ADDRESS '; 200 | case ATTRIBUTE.padding.TYPE : return type + 'PADDING '; 201 | case ATTRIBUTE.password.TYPE : return type + 'PASSWORD '; 202 | case ATTRIBUTE.priority.TYPE : return type + 'PRIORITY '; 203 | case ATTRIBUTE.realm.TYPE : return type + 'REALM '; 204 | case ATTRIBUTE.reflectedFrom.TYPE : return type + 'REFLECTED FROM '; 205 | case ATTRIBUTE.responseAddress.TYPE : return type + 'RESPONSE ADDRESS '; 206 | case ATTRIBUTE.responseOrigin.TYPE : return type + 'RESPONSE ORIGIN '; 207 | case ATTRIBUTE.responsePort.TYPE : return type + 'RESPONSE PORT '; 208 | case ATTRIBUTE.software.TYPE : return type + 'SOFTWARE '; 209 | case ATTRIBUTE.sourceAddress.TYPE : return type + 'SOURCE ADDRESS '; 210 | case ATTRIBUTE.unknownAttributes.TYPE : return type + 'UNKNOWN ATTRIBUTES '; 211 | case ATTRIBUTE.useCandidate.TYPE : return type + 'USE CANDIDATE '; 212 | case ATTRIBUTE.username.TYPE : return type + 'USERNAME '; 213 | case ATTRIBUTE.xorMappedAddress.TYPE : return type + 'XOR MAPPED ADDRESS '; 214 | default: return type + 'UNKNOWN ATTRIBUTE '; 215 | } 216 | } 217 | 218 | Attribute.lengthToString = function lengthToString ( attribute ) { 219 | var info = 'VALUE LENGTH:'; 220 | 221 | if ( attribute.length.obj < 10 ) return info + ' ' + attribute.length.obj; 222 | if ( attribute.length.obj < 100 ) return info + ' ' + attribute.length.obj; 223 | if ( attribute.length.obj < 1000 ) return info + ' ' + attribute.length.obj; 224 | 225 | return new Error('INVALID VALUE LENGTH'); 226 | } 227 | 228 | Attribute.valueToString = function valueToString ( attribute ) { 229 | return 'VALUE: ' + JSON.stringify(attribute.value.obj); 230 | } 231 | 232 | Attribute.paddingToString = function paddingToString ( attribute ) { 233 | var info = 'PADDING:'; 234 | 235 | if ( attribute.padding.raw.length == 0 ) return info + ' '; 236 | if ( attribute.padding.raw.length == 1 ) return info + ' ' + attribute.padding.obj; 237 | if ( attribute.padding.raw.length == 2 ) return info + ' ' + attribute.padding.obj; 238 | if ( attribute.padding.raw.length == 3 ) return info + ' ' + attribute.padding.obj; 239 | 240 | return new Error('INVALID PADDING LENGTH'); 241 | } 242 | -------------------------------------------------------------------------------- /lib/Packet.js: -------------------------------------------------------------------------------- 1 | var util = require('./util'); 2 | var Attribute = require('./Attribute'); 3 | 4 | var Packet = module.exports = function Packet ( auth ) { 5 | this.raw = new Buffer(20); 6 | 7 | this.doc = { 8 | type: { obj: 0, raw: this.raw.slice(0, 2) }, 9 | length: { obj: 0, raw: this.raw.slice(2, 4) }, 10 | cookie: { obj: '', raw: this.raw.slice(4, 8) }, 11 | transactionID: { obj: '', raw: this.raw.slice(8, 20) }, 12 | 13 | attribute: { }, 14 | attributes: [ ] 15 | } 16 | 17 | this.auth = auth || { username: '', password: '' } 18 | 19 | if ( this.auth.username == null ) this.auth.username = ''; 20 | if ( this.auth.password == null ) this.auth.password = ''; 21 | 22 | Attribute.expand(this); 23 | 24 | this.type = this.length = 0; 25 | this.cookie = Packet.MAGIC_COOKIE; 26 | this.transactionID = util.generate(12); 27 | } 28 | 29 | Packet.prototype = { 30 | get type ( ) { return Packet.getType(this); }, 31 | set type ( value ) { Packet.setType(this, value); }, 32 | 33 | get length ( ) { return Packet.getLength(this); }, 34 | set length ( value ) { Packet.setLength(this, value); }, 35 | 36 | get cookie ( ) { return Packet.getCookie(this); }, 37 | set cookie ( value ) { Packet.setCookie(this, value); }, 38 | 39 | get transactionID ( ) { return Packet.getTransactionID(this); }, 40 | set transactionID ( value ) { Packet.setTransactionID(this, value); }, 41 | 42 | get attribute ( ) { return Packet.getAttribute(this); }, 43 | get attributes ( ) { return Packet.getAttributes(this); } 44 | } 45 | 46 | Packet.prototype.append = function append ( attribute ) { 47 | if ( attribute.raw.length % 4 != 0 ) { 48 | return new Error('invalid attribute'); 49 | } 50 | 51 | this.raw = Buffer.concat([ this.raw, attribute.raw ]); 52 | 53 | this.doc.type.raw = this.raw.slice(0, 2); 54 | this.doc.length.raw = this.raw.slice(2, 4); 55 | this.doc.cookie.raw = this.raw.slice(4, 8); 56 | this.doc.transactionID.raw = this.raw.slice(8, 20); 57 | 58 | this.length = this.raw.length - 20; 59 | this.doc.attributes.push(attribute); 60 | 61 | if ( !this.doc.attribute[attribute.name] ) { 62 | this.doc.attribute[attribute.name] = attribute; 63 | } 64 | } 65 | 66 | Packet.prototype.valueOf = function valueOf ( ) { 67 | return { 68 | type : this.type, 69 | length : this.length, 70 | cookie : this.cookie, 71 | transactionID : this.transactionID, 72 | attribute : this.attribute, 73 | attributes : this.attributes 74 | } 75 | } 76 | 77 | Packet.prototype.toString = function toString ( ) { 78 | var raw = '\033[37m' + this.raw.toString('hex') + '\033[0m'; 79 | var header = [ 80 | Packet.typeToString(this), 81 | '=>', 82 | Packet.lengthToString(this), 83 | '-', 84 | Packet.cookieToString(this), 85 | '-', 86 | Packet.transactionIDToString(this) 87 | ].join(' '); 88 | 89 | var string = [ raw, header ]; 90 | 91 | for ( var i = 0; i < this.doc.attributes.length; i += 1 ) { 92 | string.push(' ' + this.doc.attributes[i].toString()); 93 | } 94 | 95 | return string.join('\n'); 96 | } 97 | 98 | 99 | Packet.getType = function getType ( packet ) { 100 | return packet.doc.type.obj; 101 | } 102 | 103 | Packet.setType = function setType ( packet, value ) { 104 | var value = value || 0; 105 | 106 | packet.doc.type.obj = value; 107 | packet.doc.type.raw.writeUInt16BE(value, 0); 108 | } 109 | 110 | Packet.getLength = function getLength ( packet ) { 111 | return packet.doc.length.obj; 112 | } 113 | 114 | Packet.setLength = function setLength ( packet, value ) { 115 | var value = value || 0; 116 | 117 | packet.doc.length.obj = value; 118 | packet.doc.length.raw.writeUInt16BE(value, 0); 119 | } 120 | 121 | Packet.getCookie = function getCookie ( packet ) { 122 | return packet.doc.cookie.obj; 123 | } 124 | 125 | Packet.setCookie = function setCookie ( packet, value ) { 126 | var value = value || Packet.MAGIC_COOKIE; 127 | 128 | packet.doc.cookie.obj = value; 129 | packet.doc.cookie.raw.write(value, 0, 4, 'hex'); 130 | } 131 | 132 | Packet.getTransactionID = function getTransactionID ( packet ) { 133 | return packet.doc.transactionID.obj; 134 | } 135 | 136 | Packet.setTransactionID = function setTransactionID ( packet, value ) { 137 | var value = value || util.generate(12); 138 | 139 | packet.doc.transactionID.obj = value; 140 | packet.doc.transactionID.raw.write(value, 0, 12, 'hex'); 141 | } 142 | 143 | 144 | Packet.getAttribute = function getAttribute ( packet ) { 145 | var attribute = { } 146 | 147 | for ( var name in packet.doc.attribute ) { 148 | attribute[name] = packet.doc.attribute[name].value.obj; 149 | } 150 | 151 | return attribute; 152 | } 153 | 154 | Packet.getAttributes = function getAttributes ( packet ) { 155 | var attributes = [ ]; 156 | 157 | for ( var i = 0; i < packet.doc.attributes.length; i += 1 ) { 158 | attributes[i] = packet.doc.attributes[i].value.obj; 159 | } 160 | 161 | return attributes; 162 | } 163 | 164 | 165 | Packet.parse = function parse ( data, auth ) { 166 | if ( !Packet.parse.check(data) ) { 167 | return new Error('not a stun packet'); 168 | } 169 | 170 | var packet = new Packet(auth); 171 | 172 | packet.type = data.readUInt16BE(0); 173 | packet.length = data.readUInt16BE(2); 174 | packet.cookie = data.toString('hex', 4, 8); 175 | packet.transactionID = data.toString('hex', 8, 20); 176 | 177 | while ( packet.raw.length != data.length ) { 178 | var error, attribute = Attribute.parse(packet, data); 179 | 180 | if ( attribute instanceof Error ) { 181 | return attribute; 182 | } 183 | 184 | if ( error = packet.append(attribute) ) return error; 185 | } 186 | 187 | return packet; 188 | } 189 | 190 | Packet.parse.check = function parseCheck ( data ) { 191 | if ( data.length < 20 ) return false; 192 | if ( data.length % 4 != 0 ) return false; 193 | if ( data.readUInt8(0) >= 64 ) return false; 194 | if ( data.readUInt16BE(2) + 20 != data.length ) return false; 195 | 196 | return true; 197 | } 198 | 199 | 200 | Packet.typeToString = function typeToString ( packet ) { 201 | var type = '(0x' + packet.doc.type.raw.toString('hex') + ') '; 202 | 203 | switch ( packet.doc.type.obj ) { 204 | case Packet.BINDING_REQUEST: return type + 'BINDING REQUEST' + ' '; 205 | case Packet.BINDING_SUCCESS: return type + 'BINDING SUCCESS' + ' '; 206 | case Packet.BINDING_FAILURE: return type + 'BINDING FAILURE' + ' '; 207 | case Packet.SSECRET_REQUEST: return type + 'SSECRET REQUEST' + ' '; 208 | case Packet.SSECRET_SUCCESS: return type + 'SSECRET SUCCESS' + ' '; 209 | case Packet.SSECRET_FAILURE: return type + 'SSECRET FAILURE' + ' '; 210 | default: return type + 'UNKNOWN MESSAGE' + ' '; 211 | } 212 | } 213 | 214 | Packet.lengthToString = function lengthToString ( packet ) { 215 | var info = 'PAYLOAD LENGTH:'; 216 | 217 | if ( packet.doc.length.obj < 10 ) return info + ' ' + packet.doc.length.obj; 218 | if ( packet.doc.length.obj < 100 ) return info + ' ' + packet.doc.length.obj; 219 | if ( packet.doc.length.obj < 1000 ) return info + ' ' + packet.doc.length.obj; 220 | if ( packet.doc.length.obj < 10000 ) return info + ' ' + packet.doc.length.obj; 221 | 222 | return new Error('INVALID PAYLOAD LENGTH'); 223 | } 224 | 225 | Packet.cookieToString = function cookieToString ( packet ) { 226 | //return '(0x' + packet.doc.cookie.raw.toString('hex') + ') COOKIE: ' + packet.doc.cookie.obj; 227 | return 'COOKIE: ' + '"' + packet.doc.cookie.obj + '"'; 228 | } 229 | 230 | Packet.transactionIDToString = function transactionIDToString ( packet ) { 231 | //return '(0x' + packet.doc.transactionID.raw.toString('hex') + ') TRANSACTION ID: ' + packet.doc.transactionID.obj; 232 | return 'TRANSACTION ID: ' + '"' + packet.doc.transactionID.obj + '"'; 233 | } 234 | 235 | Packet.MAGIC_COOKIE = '2112a442'; 236 | 237 | Packet.BINDING_REQUEST = 0x0001; 238 | Packet.BINDING_SUCCESS = 0x0101; 239 | Packet.BINDING_FAILURE = 0x0111; 240 | 241 | Packet.SSECRET_REQUEST = 0x0002; 242 | Packet.SSECRET_SUCCESS = 0x0102; 243 | Packet.SSECRET_FAILURE = 0x0112; 244 | 245 | 246 | if ( require.main === module ) { 247 | console.log('Testing Packet at "' + __filename + '"...'); 248 | 249 | var auth = { 250 | username: 'evtj:h6vY', 251 | password: 'VOkJxbRl1RmTxUk/WvJxBt' 252 | } 253 | 254 | var raw = { 255 | request: [ 256 | '00010058', '2112a442', 'b7e7a701', 'bc34d686', 257 | 'fa87dfae', '80220010', '5354554e', '20746573', 258 | '7420636c', '69656e74', '00240004', '6e0001ff', 259 | '80290008', '932ff9b1', '51263b36', '00060009', 260 | '6576746a', '3a683676', '59202020', '00080014', 261 | '9aeaa70c', 'bfd8cb56', '781ef2b5', 'b2d3f249', 262 | 'c1b571a2', '80280004', 'e57a3bcf' 263 | ].join(''), 264 | response: [ 265 | '0101003c', '2112a442', 'b7e7a701', 'bc34d686', 266 | 'fa87dfae', '8022000b', '74657374', '20766563', 267 | '746f7220', '00200008', '0001a147', 'e112a643', 268 | '00080014', '2b91f599', 'fd9e90c3', '8c7489f9', 269 | '2af9ba53', 'f06be7d7', '80280004', 'c07d4c96' 270 | ].join('') 271 | } 272 | 273 | var request = new Packet(auth); 274 | 275 | request.type = Packet.BINDING_REQUEST; 276 | request.transactionID = 'b7e7a701bc34d686fa87dfae'; 277 | request.append.software('STUN test client'); 278 | request.append.priority(0x6e0001ff); 279 | request.append.iceControlled('932ff9b151263b36'); 280 | request.append.username(auth.username, '20'); 281 | request.append.messageIntegrity(); 282 | request.append.fingerprint(); 283 | 284 | var response = new Packet(auth); 285 | 286 | response.type = Packet.BINDING_SUCCESS; 287 | response.transactionID = 'b7e7a701bc34d686fa87dfae'; 288 | response.append.software('test vector', '20'); 289 | response.append.xorMappedAddress({ host: '192.0.2.1', port: '32853' }); 290 | request.append.messageIntegrity(); 291 | response.append.fingerprint(); 292 | 293 | var prequest = Packet.parse(new Buffer(raw.request, 'hex'), auth); 294 | var presponse = Packet.parse(new Buffer(raw.response, 'hex'), auth); 295 | 296 | var hex = { 297 | request: request.raw.toString('hex'), 298 | response: response.raw.toString('hex'), 299 | prequest: prequest.raw.toString('hex'), 300 | presponse: presponse.raw.toString('hex') 301 | } 302 | 303 | var result = { request: '', response: '', prequest: '', presponse: '' } 304 | 305 | for ( var i = 0; i < hex.request.length; i += 2 ) { 306 | var hbyte = hex.request[i] + hex.request[i + 1]; 307 | var rbyte = raw.request[i] + raw.request[i + 1]; 308 | var color = hbyte == rbyte ? '\033[32m' : '\033[31m'; 309 | 310 | result.request += color + hbyte + '\033[0m'; 311 | } 312 | 313 | for ( var i = 0; i < hex.response.length; i += 2 ) { 314 | var hbyte = hex.response[i] + hex.response[i + 1]; 315 | var rbyte = raw.response[i] + raw.response[i + 1]; 316 | var color = hbyte == rbyte ? '\033[32m' : '\033[31m'; 317 | 318 | result.response += color + hbyte + '\033[0m'; 319 | } 320 | 321 | for ( var i = 0; i < hex.prequest.length; i += 2 ) { 322 | var hbyte = hex.prequest[i] + hex.prequest[i + 1]; 323 | var rbyte = raw.request[i] + raw.request[i + 1]; 324 | var color = hbyte == rbyte ? '\033[32m' : '\033[31m'; 325 | 326 | result.prequest += color + hbyte + '\033[0m'; 327 | } 328 | 329 | for ( var i = 0; i < hex.presponse.length; i += 2 ) { 330 | var hbyte = hex.presponse[i] + hex.presponse[i + 1]; 331 | var rbyte = raw.response[i] + raw.response[i + 1]; 332 | var color = hbyte == rbyte ? '\033[32m' : '\033[31m'; 333 | 334 | result.presponse += color + hbyte + '\033[0m'; 335 | } 336 | 337 | console.log(result.request); 338 | console.log(raw.request); 339 | console.log(request.toString()); 340 | 341 | console.log(result.response); 342 | console.log(raw.response); 343 | console.log(response.toString()); 344 | 345 | console.log(result.prequest); 346 | console.log(raw.request); 347 | console.log(prequest.toString()); 348 | 349 | console.log(result.presponse); 350 | console.log(raw.response); 351 | console.log(presponse.toString()); 352 | 353 | 354 | var packet = new Packet(); 355 | 356 | var tieBreaker = '08192a3b4c5e6d7f'; 357 | var attributes = [ 0x02, 0x04, 0x05 ]; 358 | var flags = { host: true, port: true } 359 | var realm = 'realm', nonce = 'nonce'; 360 | var username = 'name', password = 'pswd'; 361 | var software = "soft", priority = 12345; 362 | var port = 1234, padding = 'some string for padding....'; 363 | var errorCode = { code: 300, reason: 'Try Alternate' } 364 | var address = { host: '192.168.0.1', port: 8080, family: 'IPv4' } 365 | 366 | packet.append.responseAddress(address); 367 | packet.append.changedAddress(address); 368 | packet.append.sourceAddress(address); 369 | packet.append.password(password); 370 | packet.append.reflectedFrom(address); 371 | 372 | packet.append.priority(priority); 373 | packet.append.useCandidate(); 374 | packet.append.iceControlled(tieBreaker); 375 | packet.append.iceControlling(tieBreaker); 376 | 377 | packet.append.mappedAddress(address); 378 | packet.append.xorMappedAddress(address); 379 | packet.append.username(username); 380 | packet.append.messageIntegrity(); 381 | packet.append.fingerprint(); 382 | packet.append.errorCode(errorCode); 383 | packet.append.realm(realm); 384 | packet.append.nonce(nonce); 385 | packet.append.unknownAttributes(attributes); 386 | packet.append.software(software); 387 | packet.append.alternateServer(address); 388 | 389 | packet.append.changeRequest(flags); 390 | packet.append.responseOrigin(address); 391 | packet.append.otherAddress(address); 392 | packet.append.responsePort(port); 393 | packet.append.padding(padding); 394 | 395 | console.log(String(packet)); 396 | } 397 | --------------------------------------------------------------------------------