Created
September 30, 2010 17:40
-
-
Save TooTallNate/604979 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 | |
usage: | |
================================================================================ | |
var sys = require("sys"); | |
var upnp = require("./upnp"); | |
var timeout = 5000; //ms | |
upnp.searchGateway(timeout, function(err, gateway) { | |
if (err) throw err; | |
sys.puts("Found Gateway!"); | |
sys.print("Fetching External IP ... "); | |
gateway.getExternalIP(function(err, ip) { | |
if (err) throw err; | |
sys.puts(ip); | |
sys.print("Mapping port 80->192.168.0.6:8080 ... "); | |
gateway.AddPortMapping( | |
"TCP" // or "UDP" | |
, 80 // External port | |
, 8080 // Internal Port | |
, "192.168.0.6" // Internal Host (ip address of your pc) | |
, "my web server" // Description. | |
, function(err) { | |
if (err) throw err; | |
sys.puts("Success"); | |
sys.puts("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() { | |
process.binding('net').setBroadcast(socket.fd, true); | |
// send a few packets just in case. | |
for (var i = 0; i < 4; i++) | |
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