-
-
Save Befzz/73ae7a094dcf41144e9c to your computer and use it in GitHub Desktop.
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 / 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