/* node UPNP port forwarding PoC This is a simple way to forward ports on NAT routers with UPNP. This is a not-for-production hack that I found useful when testing apps on my home network behind ny NAT router. -satori / edited by smolleyes for freebox v6 usage: (install/clone node-ip from https://github.com/indutny/node-ip) ================================================================================ ================================================================================ */ var url = require("url"); var http = require("http"); var dgram = require("dgram"); var Buffer = require("buffer").Buffer; // some const strings - dont change const SSDP_PORT = 1901; const bcast = "239.255.255.250"; const ST = "urn:schemas-upnp-org:device:InternetGatewayDevice:1"; const req = "M-SEARCH * HTTP/1.1\r\nHost:239.255.255.250:1900\r\n\ ST:"+ST+"\r\nMan:\"ssdp:discover\"\r\nMX:3\r\n\r\n"; const WANIP = "urn:schemas-upnp-org:service:WANIPConnection:1"; const OK = "HTTP/1.1 200 OK"; const SOAP_ENV_PRE = "\n\n"; const SOAP_ENV_POST = "\n\n"; function searchGateway(timeout, callback) { var self = this; var reqbuf = new Buffer(req, "ascii"); var socket = new dgram.Socket('udp4'); var clients = {}; var t; if (timeout) { t = setTimeout(function() { onerror(new Error("searchGateway() timed out")); }, timeout); } var onlistening = function() { socket.setBroadcast(socket.fd, true); // send a few packets just in case. socket.send(reqbuf, 0, reqbuf.length, SSDP_PORT,bcast); } var onmessage = function(message, rinfo) { console.log('message?') message = message.toString(); if (message.substr(0, OK.length) !== OK || !message.indexOf(ST) || !message.indexOf("location:")) return; var l = url.parse(message.match(/location:(.+?)\r\n/i)[1].trim()); if (clients[l.href]) return; var client = clients[l.href] = http.createClient(l.port, l.hostname); var request = client.request("GET", l.pathname, {"host": l.hostname}); request.end(); request.addListener('response', function (response) { if (response.statusCode !== 200) return; var resbuf = ""; response.addListener('data', function (chunk) { resbuf += chunk }); response.addListener("end", function() { resbuf = resbuf.substr(resbuf.indexOf(WANIP) + WANIP.length); var ipurl = resbuf.match(/(.+?)<\/controlURL>/i)[1].trim() socket.close(); clearTimeout(t); callback(null, new Gateway(l.port, l.hostname, ipurl)); }); }); } var onerror = function(err) { socket.close() ; clearTimeout(t); callback(err); } var onclose = function() { socket.removeListener("listening", onlistening); socket.removeListener("message", onmessage); socket.removeListener("close", onclose); socket.removeListener("error", onerror); } socket.addListener("listening", onlistening); socket.addListener("message", onmessage); socket.addListener("close", onclose); socket.addListener("error", onerror); socket.bind(SSDP_PORT); } exports.searchGateway = searchGateway; function Gateway(port, host, path) { this.port = port; this.host = host; this.path = path; } Gateway.prototype.getExternalIP = function(callback) { var s = "\ \n"; this._getSOAPResponse(s, "GetExternalIPAddress", function(err, xml) { if (err) callback(err); else callback(null, xml.match(/(.+?)<\/NewExternalIPAddress>/i)[1]); }); } Gateway.prototype.AddPortMapping = function(protocol , extPort , intPort , host , description , callback) { var s = "\ \ "+extPort+"\ "+protocol+"\ "+intPort+"\ "+host+"\ 1\ "+description+"\ 0\ "; this._getSOAPResponse(s, "AddPortMapping", callback); } Gateway.prototype._getSOAPResponse = function(soap, func, callback) { var s = [SOAP_ENV_PRE, soap, SOAP_ENV_POST].join(""); var client = http.createClient(this.port, this.host); var hdrs = { "host" : this.host , "SOAPACTION" : "\"" + WANIP + "#" + func + "\"" , "content-type" : "text/xml" , "content-length" : s.length }; var request = client.request("POST", this.path, hdrs); request.end(s); request.addListener('response', function (response) { if (response.statusCode !== 200) { response.close(); callback(new Error("Invalid SOAP action")); return; } var buf = ""; response.addListener('data', function (chunk) { buf += chunk }); response.addListener('end', function () { callback(null, buf) }); }); }