Skip to content

Instantly share code, notes, and snippets.

@acacio
Created January 12, 2012 21:17
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save acacio/1603181 to your computer and use it in GitHub Desktop.
Save acacio/1603181 to your computer and use it in GitHub Desktop.
UPNP Port Forwarding for node.js
/* 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) });
});
}
@bram-dingelstad
Copy link

Thanks man, I'm gonna try to use this in my project, I'll link to this gist :D

@bram-dingelstad
Copy link

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

@smolleyes
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment