2

I'm attempting to write a small relay script in node.js that listens for incoming TCP connections on a local socket, and when it gets one, forwards the traffic to a 3rd party. It must also take any returned data from that 3rd party and send it back to the original local socket. I've tried code like http://delog.wordpress.com/2011/07/19/a-tcp-relay-mechanism-with-node-js/ and it does work, but it requires the sender be a server that is listening on a socket itself, and my utility is intended to work with any program that tries to create an outbound TCP connection. Unfortunately, the problem I'm running into is that everything works great the first time with the client sending the data to the "router" program, and the router forwarding it to another server, and then returning the data from the client. However, when the client program ends or is terminated and attempts to reconnect, I get this:

events.js:72
    throw er; // Unhandled 'error' event
          ^
Error: This socket has been ended by the other party
at Socket.writeAfterFIN [as write] (net.js:275:12)
at Socket.<anonymous> (/root/tcp_loop.js:37:17)
at Socket.emit (events.js:117:20)
at Socket.<anonymous> (_stream_readable.js:748:14)
at Socket.emit (events.js:92:17)
at emitReadable_ (_stream_readable.js:410:10)
at emitReadable (_stream_readable.js:406:5)
at readableAddChunk (_stream_readable.js:168:9)
at Socket.Readable.push (_stream_readable.js:130:10)
at TCP.onread (net.js:528:21)

I ripped out all of the logic and distilled the test case into a small bit of code: one server that acts as both the router (listening on port 8124) as well as the "remote" server (on port 9999), though my testing indicates it makes no difference weather the remote server is on the same machine, on the Internet, etc. Here is the server code:

var net = require('net'),
    util = require('util')
;

// The loop_server simulates a remote service.
// The error occurs whether using it here, or actually forwarding
// the data to a remote host.
var loop_server = net.createServer(function(loop) {
    console.log("Loop server connected");
    loop.on("end", function() {
        console.log("Loop server disconnected");
    });
    loop.on("data", function(data) {
        console.log("Loop got data: " + data);
        loop.write(data);
    });
}).listen(9999, function() {
    console.log("Loop server bound");
});

var remote_socket = net.connect(9999, function() {
    console.log("Remote connected");

    var local_server = net.createServer(function(local_socket) { //'connection' listener
        console.log('Local server connected');
        local_socket.on('end', function() {
            console.log('Local server disconnected');
            // local_socket.destroy();
        });

        local_socket.on('data', function(ldata) {
            console.log("Local socket got data: " + ldata);
            remote_socket.write(ldata);
        });

        remote_socket.on('data', function(rdata) {
            console.log("Remote socket got data: " + rdata);
            local_socket.write(rdata);
        });

        local_socket.write('hello\r\n');
    }).listen(8124, function() { //'listening' listener
        console.log('Local server bound');
    });
}); // remote_socket

The thing that's failing is the local_socket.write(rdata); in the remote_socket.on('data', ... handler. It works the first time the router is started and the client connects, but never again.

For reference, here is the code for the little client app that I've been using. I get the same result with a perl script, telnet, etc.:

var net = require('net');

var client = new net.Socket();
client.connect(8124, function() {
    console.log('CONNECTED TO: localhost:8124');
    client.write('Single text message from the client app');
});

client.on('data', function(data) {
    console.log('DATA: ' + data);
});

client.on('close', function() {
    sconsole.log('Connection closed');
});

Any insight would be greatly appreciated. I feel like I must be missing something extremely simple here...

Update:

Nitzin's solution below is a better way to do this, but in my particular example below, the solution is to remove old remote_socket.on('data') listeners before creating new ones, e.g.:

var remote_socket = net.connect(9999, function() {
    console.log("Remote connected");

    var local_server = net.createServer(function(local_socket) { //'connection' listener
        console.log('Local server connected');

        remote_socket.removeAllListeners('data');

        ...

        remote_socket.on('data', function(rdata) {
            console.log("Remote socket got data: " + rdata);
            local_socket.write(rdata);
        });

1 Answer 1

3

You should not destroy the socket. It closes both ends of the socket. You should only .end() it, which closes your writing end.

EDIT

Destroying the socket is bad, as I originally wrote, but your real problem is something completely different: you got your proxy (what you call "local") and echo (what you call "remote") servers backwards: the proxy server should make a new connection to the echo server for each new connection the proxy server gets, not the other way around as you have it now.

The only end() needed is in the client, to let the server know you're done writing.

Here is client.js:

var net = require('net');

var client = new net.Socket();

client.connect(8124, function() {
    console.log('CLIENT: CONNECTED: localhost:8124');
    client.write('single text message from the client app');
    client.end();
});

client.on('data', function(data) {
    console.log('CLIENT: GOT DATA: ' + data);
});

client.on('close', function() {
    console.log('CLIENT: CONNECTION CLOSED');
});

And here is servers.js:

var net = require('net'),
    util = require('util');

net.createServer(function(conn) {
    console.log('ECHO_SERVER: CONN: new connection');
    conn.on('end', function() {
        console.log('ECHO_SERVER: CONN: disconnected');
    });
    conn.on('data', function(data) {
        console.log('ECHO_SERVER: CONN: GOT DATA: ' + data);
        conn.write(data);
    });
}).listen(9999, function() {
    console.log('ECHO_SERVER STARTED');
});

net.createServer(function(conn) {

    console.log('PROXY_SERVER: CONN: new connection');

    var remote = net.connect(9999, function() {
        console.log('PROXY_SERVER: CONNECTED TO ECHO_SERVER');
        conn.on('end', function() {
            console.log('PROXY_SERVER: CONN: disconnected');
            remote.end();
        });
        conn.on('data', function(data) {
            console.log('PROXY_SERVER: CONN: GOT DATA FOR  ECHO_SERVER: ' + data);
            remote.write(data);
        });
        remote.on('data', function(data) {
            console.log('PROXY_SERVER: CONN: GOT DATA FROM ECHO_SERVER: ' + data);
            conn.write(data);
        });
    });

}).listen(8124, function() {
    console.log('PROXY_SERVER STARTED');
});

As you can see, for each conn to the proxy server, there is a new remote going to the echo server.

Sign up to request clarification or add additional context in comments.

4 Comments

Hi Nitzan. Thanks for the reply. Unfortunately, removing the destroy() doesn't fix the issue, and it seems to introduce another problem - the client doesn't "hang up" after getting the response from the server. I think I might be misunderstanding what net.createServer(1234,function(c){...} does too. I thought that for every new connection the server receives, it would make a new "c", but instead it seems like there is only one "c".
Did you add .end() instead of .destroy()?
Also, your loop_server should call .end() in its .on('end', ...)
Thanks for the detailed response! I'm going to mark your answer as accepted because it's the 'right" thing to do, code-wise. However, I actually found the root cause of my issue in the original code: I was adding new event handlers to remote_socket without deleting the old ones (from the previous connection) first, so the first listener remote_socket would encounter on('data') would be referencing the old local_socket instead of the new one. Once I added a remotesocket.removeAllListeners()` to the local_socket disconnect block, the issue was resolved.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.