Skip to content

Instantly share code, notes, and snippets.

@Befzz
Forked from acacio/upnp.js
Last active August 29, 2015 14:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Befzz/73ae7a094dcf41144e9c to your computer and use it in GitHub Desktop.
Save Befzz/73ae7a094dcf41144e9c to your computer and use it in GitHub Desktop.
/* 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 / edited by Befzz
================================================================================
var upnp = require("./upnp");
var myIp = "192.168.0.100";
var timeout = 15000; //ms
upnp.searchGateway({'timeout':timeout}, function(err, gateway) {
if (err) throw err;
console.log("Found Gateway!");
var maps = [];
function get_map_rules(_callback) {
gateway.GetGenericPortMappingEntry(maps.length, function(err, msg, data){
if(err==null){
maps.push(data);
setTimeout(get_map_rules, 0,_callback);
} else {
_callback();
}
});
}
get_map_rules(function(){
for(var i=0;i<maps.length;i++) {
console.log("Rule:", i, maps[i]['NewInternalClient'], maps[i]['NewPortMappingDescription']);
}
});
console.log("Fetching External IP ... ");
gateway.getExternalIP(function(err, ip) {
if (err) throw err;
console.log("External IP: " +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)
, "Test port map :D" // Description.
, function(err,buf) {
if (err) throw err;
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 ST = "ssdp:all"
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(options, callback) {
var reqbuf = new Buffer(req, "ascii");
var socket = new dgram.Socket('udp4');
var clients = {};
var t;
if (options.timeout) {
t = setTimeout(function() {
onerror(new Error("searchGateway() timed out"));
}, options.timeout);
}
var onlistening = function() {
console.log('listen!',socket.address());
//socket.setBroadcast(true);
socket.send(reqbuf, 0, reqbuf.length, SSDP_PORT, bcast);
}
var onmessage = function(message, rinfo) {
message = message.toString();
console.log(message)
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) {
console.log('err',err)
socket.close() ;
clearTimeout(t);
callback(err);
}
var onclose = function() {
console.log('closed')
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(options.port?options.port:1025 + Math.round(Math.random()*60000),options.ip?options.ip:null);
}
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.GetGenericPortMappingEntry = function(NewPortMappingIndex, callback) {
var s = "<u:GetGenericPortMappingEntry xmlns:u=\""+WANIP+"\">\
<NewPortMappingIndex>"+NewPortMappingIndex+"</NewPortMappingIndex>\
</u:GetGenericPortMappingEntry>";
this._getSOAPResponse(s, "GetGenericPortMappingEntry", function(err,msg){
//console.log("SOAP:", err, msg);
var result = {};
for(var i=0,m;i<GetGenericPortMappingEntry_arguments.length;i++) {
m = msg.match("<"+GetGenericPortMappingEntry_arguments[i]
+">(.*?)<\/"+GetGenericPortMappingEntry_arguments[i]+">");
if(m != null) {
result[GetGenericPortMappingEntry_arguments[i]] = m[1];
}
}
//console.log(result);
if(typeof(callback) == 'function') {
callback(err,msg,result);
}
});
}
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);
}
const GetGenericPortMappingEntry_arguments = [
'NewRemoteHost',
'NewExternalPort',
'NewProtocol',
'NewInternalPort',
'NewInternalClient',
'NewEnabled',
'NewPortMappingDescription',
'NewLeaseDuration'
];
Gateway.prototype._getSOAPResponse = function(soap, func, callback) {
var s = [SOAP_ENV_PRE, soap, SOAP_ENV_POST].join("");
var request = http.request({
'port': this.port,
'host': this.host,
'method': 'POST',
'path': this.path,
headers:{
"host" : this.host
, "SOAPACTION" : "\"" + WANIP + "#" + func + "\""
, "content-type" : "text/xml"
, "content-length" : s.length }
});
request.end(s);
request.addListener('response', function (response) {
var buf = "";
response.addListener('data', function (chunk) { buf += chunk });
response.addListener('end', function () { callback((response.statusCode !== 200?new Error("Invalid SOAP action"):null), buf) });
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment