Created
January 12, 2012 21:17
-
-
Save acacio/1603181 to your computer and use it in GitHub Desktop.
UPNP Port Forwarding for node.js
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(); | |
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(SSDP_PORT, bcast, reqbuf, 0, reqbuf.length); | |
} | |
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) }); | |
}); | |
} |
Quick question, node says I need to use upn6 or upn4 as a socket type
Error: Bad socket type specified. Valid types are: udp4, udp6
updated gist here:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks man, I'm gonna try to use this in my project, I'll link to this gist :D