-
-
Save smolleyes/a3dea6e5086dd4d064f6 to your computer and use it in GitHub Desktop.
simple upnp port mapping with nodejs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 upnp = require("./upnp"); | |
var myIp = require("node-ip").address(); | |
var timeout = 15000; //ms | |
upnp.searchGateway(timeout, function(err, gateway) { | |
if (err) throw err; | |
console.log("Found Gateway!"); | |
console.log("Fetching External IP ... "); | |
gateway.getExternalIP(function(err, ip) { | |
if (err) throw err; | |
console.log(ip); | |
console.log("Mapping port 8888->"+myIp+":8888 ... "); | |
gateway.AddPortMapping( | |
"TCP" // or "UDP" | |
, 8888 // External port | |
, 8888 // Internal Port | |
, myIp // Internal Host (ip address of your pc) | |
, "YOUR DESCRIPTION" // Description. | |
, function(err) { | |
if (err) throw err; | |
console.log("Success"); | |
console.log("Done."); | |
}); | |
}); | |
}); | |
================================================================================ | |
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 = 1900; | |
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 = "<?xml version=\"1.0\"?>\n<s:Envelope \ | |
xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" \ | |
s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body>\n"; | |
const SOAP_ENV_POST = "</s:Body>\n</s:Envelope>\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) { | |
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>(.+?)<\/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 = | |
"<u:GetExternalIPAddress xmlns:u=\"" + WANIP + "\">\ | |
</u:GetExternalIPAddress>\n"; | |
this._getSOAPResponse(s, "GetExternalIPAddress", function(err, xml) { | |
if (err) callback(err); | |
else callback(null, | |
xml.match(/<NewExternalIPAddress>(.+?)<\/NewExternalIPAddress>/i)[1]); | |
}); | |
} | |
Gateway.prototype.AddPortMapping = function(protocol | |
, extPort | |
, intPort | |
, host | |
, description | |
, callback) { | |
var s = | |
"<u:AddPortMapping \ | |
xmlns:u=\""+WANIP+"\">\ | |
<NewRemoteHost></NewRemoteHost>\ | |
<NewExternalPort>"+extPort+"</NewExternalPort>\ | |
<NewProtocol>"+protocol+"</NewProtocol>\ | |
<NewInternalPort>"+intPort+"</NewInternalPort>\ | |
<NewInternalClient>"+host+"</NewInternalClient>\ | |
<NewEnabled>1</NewEnabled>\ | |
<NewPortMappingDescription>"+description+"</NewPortMappingDescription>\ | |
<NewLeaseDuration>0</NewLeaseDuration>\ | |
</u:AddPortMapping>"; | |
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) }); | |
}); | |
} | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Anyone has an updated version?