TCP hole punching in Node.js - node.js

I'm trying to punch a TCP hole through two NATs in node.js. The problem is I can't figure out how to choose which local port the connection should use?

After creating a connection with the public server, you also need to listen on the exact same local (!!) port that was used to establish that connection.
I extended your testcode to a complete tcp hole punching proof of concept:
// server.js
var server = require('net').createServer(function (socket) {
console.log('> Connect to this public endpoint with clientB:', socket.remoteAddress + ':' + socket.remotePort);
}).listen(4434, function (err) {
if(err) return console.log(err);
console.log('> (server) listening on:', server.address().address + ':' + server.address().port)
});
// clientA.js
var c = require('net').createConnection({host : 'PUBLIC_IP_OF_SERVER', port : 4434}, function () {
console.log('> connected to public server via local endpoint:', c.localAddress + ':' + c.localPort);
// do not end the connection, keep it open to the public server
// and start a tcp server listening on the ip/port used to connected to server.js
var server = require('net').createServer(function (socket) {
console.log('> (clientA) someone connected, it\s:', socket.remoteAddress, socket.remotePort);
socket.write("Hello there NAT traversal man, this is a message from a client behind a NAT!");
}).listen(c.localPort, c.localAddress, function (err) {
if(err) return console.log(err);
console.log('> (clientA) listening on:', c.localAddress + ':' + c.localPort);
});
});
// clientB.js
// read the server's output to find the public endpoint of A:
var c = require('net').createConnection({host : 'PUBLIC_IP_OF_CLIENT_A', port : PUBLIC_PORT_OF_CLIENT_A},function () {
console.log('> (clientB) connected to clientA!');
c.on('data', function (data) {
console.log(data.toString());
});
});
For a more complete version with signalling happening on the server, I refer to my code here: https://github.com/SamDecrock/node-tcp-hole-punching

The socket is assigned a local port. To reuse the same port you can connect to the client using the same socket that was used to communicate with the server. This works for you because you are doing TCP hole punching. However, you cannot choose a port yourself.
Here is some test code:
// server.js
require('net').createServer(function(c) {
c.write(c.remotePort.toString(10));
}).listen(4434);
//client.js
var c = require('net').createConnection({host : '127.0.0.1', port : 4434});
c.on('data', function(data) {
console.log(data.toString(), c.localPort);
c.end();
});
c.on('end', function() {
c.connect({host : '127.0.0.1', port : 4434});
});

Related

Forward Android TCP Connection to HTTP proxy in Node.js

My plan is to create a proxy of my phone 4g connection.
I've made a direct tcp connection of my phone to my pc via nodejs.
I create a client.js on my phone and server.js on my pc. They connect.
Now i need to 'transform' this direct connection into a proxy, but i dont know how.
Any help i would aprecciate.
I will show my server.js and client.js code below.
Server.js
var net = require('net');
var tcpServerPort = 7000;
// a simple TCP server for testing
var server = net.createServer(function (socket) {
console.log('Client connected to server');
socket.on('close', function () {
console.log('Client disconnected from server');
});
socket.on('data', function (buffer) {
// 'echo' server
socket.write(buffer);
});
socket.on('error', function (err) {
console.log('Error: ' + err.soString());
});
});
server.listen(tcpServerPort);
Client.js
const net = require('net');
const client = new net.Socket();
const port = 7000;
const host = 'my home ip';
client.connect(port, host, function() {
console.log('Connected');
client.write("Hello From Client " + client.address().address);
});
client.on('data', function(data) {
console.log('Server Says : ' + data);
});
client.on('close', function() {
console.log('Connection closed');
});

The nodejs net.createServer function results in a blank server.address value in Bluemix

The net module has a createServer function that allows you to create a network wrapper. This works fine on a local runtime of Nodejs, but when running in Bluemix it is unable to determine the host address. The server seems to get created, but upon further inspection I find the server.address to be blank.
var tls = require('tls');
var fs = require('fs');
var net = require('net');
var tunnelHost = (process.env.VCAP_APP_HOST || 'localhost');
var tunnelPort = 8888;
var server;
var gatewayOptions = {
host: 'http://cap-sg-prd-5.integration.ibmcloud.com/',
port: '15133',
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
ca: fs.readFileSync('ca.pem')
};
console.log("starting createSecureTunnel");
//create a server end point to use as a network wrapper for the secure gateway
server = net.createServer(function (connListener){
console.log('net server created');
connListener.on('end', function() {
console.log('client disconnected');
});
connListener.on('uncaughtException', function(err){
console.log('exception caught: ' + JSON.stringify(err));
});
//connect to farside, local/private server
connectFarside(connListener, function(err, remoteSocket){
if (err){
console.log(err);
}
console.log('connection made');
remoteSocket.pipe(connListener);
console.log('remote socket connecte to local connListener');
connListener.pipe(remoteSocket);
console.log('local connListener connected to remote socket');
});
});
//setup listener for network wrapper
server.listen(tunnelPort, tunnelHost, function(){
console.log('tunnel created at: ' + tunnelHost +":"+ tunnelPort); //.address +":"+ server.address().port);
});
//createa a TLS connection to the secure gateway
function connectFarside(conn, callback) {
console.log("starting connectFarside");
try {
console.log("initiating farside connection");
var socket = tls.connect(gatewayOptions, function(){
console.log("tunnel connected to " + gatewayOptions.host +":"+ gatewayOptions.port);
callback(null, socket);
});
socket.on("error", function(err){
console.log("Socket error: " + JSON.stringify(err));
});
} catch(err) {
console.log(err);
callback(err);
}
}
Bluemix gives your app a port to run on, this is the reason it is not working in Bluemix. You are starting to start your app on port 8888 with the following line of code.
var tunnelPort = 8888;
It should be changed to
var tunnelPort = process.env.VCAP_APP_PORT || 8888;
The above line will read an environment variable called VCAP_PORT where Bluemix assigns a port to your app, if it is not running Bluemix it will run on port 8888.
Your app will be accessible over the web on port 80 and 443. Bluemix will load balance to your app for you.
You can specify the server address when listening to the server
var net = require('net')
var server = net.createServer(handler)
server.listen(port, address)
Try with address = '0.0.0.0' and see if it works
Partially solved by using the cf-autoconfig module. It helps to reconfigure modules for use on Cloud Foundry platforms. By including this as the first line in my app, it mostly works. It doesn't use the port number. But at least I can access the wrapper.
So I added this as the first line
require("cf-autoconfig");
Then I changed the server.listen to this
//setup listener for network wrapper
server.listen(tunnelPort, function(){
console.log('tunnel created at: ' + tunnelHost +":"+ tunnelPort); //.address +":"+ server.address().port);
});
Now if I use my app name, I can connect to the server created by net.createServer().
I would still like to know how to get the port to work, so this can be used inside of a web application to provide the tunneling.

Tunnel between servers that supports any port

I'm trying to create a tunnel using Node.js that will allow me to access Server X from Server Y. Server X is connected to a router that isn't port forwarded and I won't know the IP of Server X until it connects, which means that Server X has to open a socket to Server Y and not the other way round.
I've successfully created a version of this using socket.io. Server X opens a socket to server Y, the user can then access Server Y in a web browser and Server Y proxies the requests down the socket to Server X.
What I would like to do is allow access to any kind of port on Server X, and forward not just web requests but requests of any kind. For example, I'd like to allow forwarding of SSH so I can access SSH on Server X through Server Y (doesn't have to be port 22). localtunnel.me is an existing service which is an exact example of what I want to achieve.
Are there any libraries that could help me achieve this, or can I build it from the ground up quite easily? I built the web request tunnel easily, perhaps it can be adapted to support not just web traffic? I've attached the code to my existing web tunnel below.
Server X (connects to Server Y on port 3001, receives requests for data and sends it back:
var socket = require('socket.io-client')('http://localhost:3001');
socket.on('connect', function(){
console.log('Connected');
// Register the event for request of data
socket.on('request', function(data){
// Get the path
var options = {
host: 'localhost',
port: 3000,
path: data.path,
method: data.method
};
var request = http.get(options, function(resp){
resp.on('data', function(chunk){
socket.emit('response', { html: chunk });
// Probably need to fix this for file transfers. Use resp.on('end'
});
}).on("error", function(e){
console.log("Got error: " + e.message);
});
//Write our post data to the request
request.write(data.data);
//End the request.
request.end();
});
socket.on('disconnect', function(){});
});
Server Y (listens on port 3001 to connect to Server X, and listens on port 3002 for requests from user in web browser to forward to Server X:
app.listen(3001);
var rwPortalSocket;
function handler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
// Save the socket object
rwPortalSocket = socket;
});
console.log('Data channel server running at http://127.0.0.1:3001/');
// Create web server
var http = require('http');
var qs = require('querystring');
http.createServer(function (req, res) {
// Send a request
rwPortalSocket.emit('request', { path: req.url, method: req.method });
// When we get a response
rwPortalSocket.on('response', function (responseData) {
res.writeHead(200);
res.end(responseData.html);
});
}).listen(3002, '127.0.0.1');
console.log('Web server running at http://127.0.0.1:3002/');
EDIT
I've now updated my code so that should support any TCP port or packet type. The code works fine when I tell net.connect to connect to a web server, but when I tell it to connect an SSH server, my SSH client complains with Protocol error: expected packet type 31, got 20
I've added an example of my new code connecting to an SSH server below.
Server X (connects to Server Y on port 3001, receives requests for data and sends it back:
var socket = require('socket.io-client')('http://localhost:3001');
socket.on('connect', function(){
console.log('Connected');
// Connect to 22
var buff = "";
var connected = false;
var net = require('net');
var client = net.connect({host: 'myserver.com', port: 22}, function() { //'connect' listener
connected = true;
console.log('Connected to 22');
});
// Register the event for request of data
socket.on('request', function(data){
if (!connected)
{
client = net.connect({host: 'myserver.com', port: 22}, function() { //'connect' listener
connected = true;
console.log('Connected to 22');
client.write(data.data);
});
}
else
{
client.write(data.data);
}
client.setMaxListeners(0);
// When data comes back to this service, we send it on to the other server
client.on('data', function(data) {
//console.log(data.toString());
console.log('Server sent back: ' + data.toString());
if (connected)
{
socket.emit('response', { data: data });
} else {
buff += d.toString();
}
});
client.on('end', function() {
console.log('Disconnected from 22');
connected = false;
});
client.on('error', function(e) {
console.log(e);
});
console.log('Client sent: ' + data.data);
});
socket.on('disconnect', function(){});
});
Server Y (listens on port 3001 to connect to Server X, and listens on port 3002 for requests from user in SSH Client (terminal) to forward to Server X:
app.listen(3001);
var rwPortalSocket;
function handler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
// Save the socket object
rwPortalSocket = socket;
});
console.log('Data channel server running at http://127.0.0.1:3001/');
// Listen for tunnel requests
net = require('net');
var server = net.createServer(function(s) { //'connection' listener
s.on('end', function() {
console.log('server disconnected');
});
s.on('data', function (d) {
rwPortalSocket.emit('request', { data: d });
});
s.on('error', function(e) {
console.log(e);
});
s.setMaxListeners(0);
// When we get a response
rwPortalSocket.on('response', function (d) {
s.write(d.data);
});
});
server.listen(3002, function() { //'listening' listener
console.log('server bound');
});
console.log('Web server running at http://127.0.0.1:3002/');
If the "connections" to Server X would all be TCP-based, you could have an SSH server running on both ends. Server X would then connect to Server Y to only forward some port on Server Y that would point to the SSH server running on Server X. Then you could use a node module like ssh2 to connect to any port on Server X.
Another option for dynamic forwarding would be to set up a socks proxy as described in this answer. From there you could use a socks client module from npm, such as socks5-client.
If instead you have a fixed set of ports that you want have available, you could simplify the above solutions by just having an SSH server on Server Y and Server X connects and creates a port forward for each port you want to have available.
Here is an example of another option: connecting to Server X from Server Y via SSH and opening up connections on Server X using the SSH connection (via ssh2).

Node.js - "Rebind" UDP socket server to a new port

Which is the most efficient way to change a UDP server's listening port on runtime on Node.js?
I have a default port 41234 for my web app but I want the end-user to be able to change this through a settings textbox, which would call the updatePort().
Using the following code the new (neither the old) port isn't actually listening for datagrams, as I cant receive anything.
var SRV_PORT = 41234;
var server = dgram.createSocket("udp4");
server.on("error", function (err) {
print("server error:\n" + err.stack);
server.close();
});
server.on("message", function (msg, rinfo) {
//code for incoming datagram analysis here
});
server.on("listening", function () {
var address = server.address();
print("server listening " + address.address + ":" + address.port);
});
//start the UDP server with the default SRV_PORT
server.bind(SRV_PORT);
function updatePort(newPort) {
server.close();
server = dgram.createSocket("udp4");
server.bind(newPort);
}
The reason you wouldn't get any messages after updating the port is because you've assigned a new socket instance to server and do not have any event handlers attached anymore.

Create WebSockets between a TCP server and HTTP server in node.js

I have created a TCP server using Node.js which listens to clients connections.
I need to transmit data from TCP server to HTTP server again in Node.js possibly through a Websocket (socket.io).
However, I do not know how to create such connection such that TCP server is able to push data to HTTP server through Websocket.
Many Thanks.
I was trying lot of things to get this work. Most of the time I was relying on socket.io to get this working, but it was just not working with TCP.
However, net.Socket suffices the purpose.
Here is the working example of it.
TCP Server
var net = require('net');
var HOST = 'localhost';
var PORT = 4040;
var server = net.createServer();
server.listen(PORT, HOST);
server.on('connection', function(sock) {
console.log('CONNECTED: ' + sock.remoteAddress +':'+ sock.remotePort);
sock.write("TCP sending message : 1");
console.log('Server listening on ' + server.address().address +':'+
server.address().port);
}).listen(PORT, HOST);
HTTP Server
var http = require('http').createServer(httpHandler),
fs = require("fs"),
wsock = require('socket.io').listen(http),
tcpsock = require('net');
var http_port = 8888;
var tcp_HOST = 'localhost';
var tcp_PORT = 4040;
/**
* http server
*/
function httpHandler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
http.listen(http_port);
console.info("HTTP server listening on " + http_port);
wsock.sockets.on('connection', function (socket) {
var tcpClient = new tcpsock.Socket();
tcpClient.setEncoding("ascii");
tcpClient.setKeepAlive(true);
tcpClient.connect(tcp_PORT, tcp_HOST, function() {
console.info('CONNECTED TO : ' + tcp_HOST + ':' + tcp_PORT);
tcpClient.on('data', function(data) {
console.log('DATA: ' + data);
socket.emit("httpServer", data);
});
tcpClient.on('end', function(data) {
console.log('END DATA : ' + data);
});
});
socket.on('tcp-manager', function(message) {
console.log('"tcp" : ' + message);
return;
});
socket.emit("httpServer", "Initial Data");
});
Browser Client
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost');
socket.on('httpServer', function (data) {
console.log(data);
document.write(data + "\r\n");
socket.emit('tcp', "For TCP");
});
</script>
This way, there is a socket opened between HTTP server and TCP server in Node.js.
If you need to communicate server-server than websockets is probably not a best choice. Try one of RPC libraries, or just use HTTP or your own protocol.
You can use either socket.io or ws (only WebSocket) on Node.js as client (not only in browser)
var io = require('socket.io-client');
var socket = io.connect('http://IP address of Websocket server');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});

Resources