0

I have two backends. Backend A and Backend B.
Backend B sends and receives info using a socket server running at port 4243.

Then, with Backend A, I need to catch that info and save it. But I have to also have a socket server on Backend A running at port 4243.

The problem is that, when I run Backend A after running Backend B I receive the error "EADDRINUSE", because I'm using the same host:port on both apps.

If, for Backend A I use Python, the problem dissapear because I have a configuration for sockets that's called SO_REUSEADDR.
Here we have some examples:

But, I want to use JavaScript for coding my Backend A, so I was using the net package for coding the sockets, and I can't get it to work, because of the "EADDRINUSE" error.

The NodeJS documentation says that "All sockets in Node set SO_REUSEADDR already", but it doesn't seem to work for me...

This is my code so far:


// Step 0: Create the netServer and the netClient

console.log(`[DEBUG] Server will listen to: ${HOST}:${PORT}`);
console.log(`[DEBUG] Server will register with: ${AGENT_ID}`);

const netServer = net.createServer((c) => {
  console.log('[netServer] Client connected');

  c.on('message', (msg) => {
    console.log('[netServer] Received `message`, MSG:', msg.toString());
  });

  c.on('*', (event, msg) => {
    console.log('[netServer] Received `*`, EVENT:', event);
    console.log('[netServer] Received `*`, MSG:', msg);
  });

}).listen({
  host: HOST, // 'localhost',
  port: PORT, // 4243,
  family: 4, // ipv4, same as socket.AF_INET for python
});

// Code copied from nodejs documentation page (doesn't make any difference)
netServer.on('error', function (e) {
  if (e.code == 'EADDRINUSE') {
    console.log('Address in use, retrying...');
    setTimeout(function () {
      netServer.close();
      netServer.listen(PORT, HOST);
    }, 1000);
  }
});

const netClient = net.createConnection(PORT, HOST, () => {
  console.log('[netClient] Connected');
});

// Step 1: Register to instance B of DTN with agent ID 'bundlesink'

netClient.write(serializeMessage({
  messageType: AAPMessageTypes.REGISTER,
  eid: AGENT_ID,
}));

With this code, I get the following output in the terminal:

enter image description here

But, with the Python code, the socket connects successfully:

enter image description here

I don't know what to do :(
I hope I get some help here.


Edit 1

By the way, the lsof command, throws me this output for the JavaScript backend:

enter image description here

And this other output for the Python backend:

enter image description here


Edit 2

It really seems to be a problem with JavaScript. I also found this snippet:

var net = require('net');

function startServer(port, host, callback) {
    var server = net.createServer();
    server.listen(port, host, function() {
        callback(undefined, server);
    });
    server.on('error', function(error) {
        console.error('Ah damn!', error);
        callback(error);
    });
}

startServer(4000, '0.0.0.0', function(error, wildcardServer) {
    if (error) return;
    startServer(4000, '127.0.0.1', function(error, localhostServer) {
        if (error) return;
        console.log('Started both servers!');
    });
});

From this post: https://medium.com/@eplawless/node-js-is-a-liar-sometimes-8a28196d56b6

As the author says:

Well, that prints “Started both servers!” which is exactly what we don’t want.

But for me, instead of printing that, I get an error:

Ah damn! Error: listen EADDRINUSE: address already in use 127.0.0.1:4000
    at Server.setupListenHandle [as _listen2] (node:net:1319:16)
    at listenInCluster (node:net:1367:12)
    at doListen (node:net:1505:7)
    at processTicksAndRejections (node:internal/process/task_queues:84:21) {
  code: 'EADDRINUSE',
  errno: -98,
  syscall: 'listen',
  address: '127.0.0.1',
  port: 4000
}

I really cannot make it to run and print "Started both servers!".
Because that's what I want my code to do.


Edit 3

This is the Python server socket: https://gitlab.com/d3tn/ud3tn/-/blob/master/tools/aap/aap_receive.py

This is the important part:

addr = (args.tcp[0], int(args.tcp[1])) # args.tcp[0] = "localhost", args.tcp[1] = "4243"
with AAPTCPClient(address=addr) as aap_client:
  aap_client.register(args.agentid) # args.agentid = "bundlesink"
  run_aap_recv(aap_client, args.count, args.verify_pl)

It creates an AAPTCPClient, and the only thing that AAPTCPClient does, is the following:

def __init__(self, socket, address):
  self.socket = socket
  self.address = address
  self.node_eid = None
  self.agent_id = None

def register(self, agent_id=None):
  """Attempt to register the specified agent identifier.

  Args:
    agent_id: The agent identifier to be registered. If None,
        uuid.uuid4() is called to generate one.
  """
  self.agent_id = agent_id or str(uuid.uuid4())
  logger.info(f"Sending REGISTER message for '{agent_id}'...")
  msg_ack = self.send(
    AAPMessage(AAPMessageType.REGISTER, self.agent_id)
  )
  assert msg_ack.msg_type == AAPMessageType.ACK
  logger.info("ACK message received!")

def send(self, aap_msg):
  """Serialize and send the provided `AAPMessage` to the AAP endpoint.

  Args:
      aap_msg: The `AAPMessage` to be sent.
  """
  self.socket.send(aap_msg.serialize())
  return self.receive()

def receive(self):
  """Receive and return the next `AAPMessage`."""
  buf = bytearray()
  msg = None
  while msg is None:
    data = self.socket.recv(1)
    if not data:
      logger.info("Disconnected")
      return None
    buf += data
    try:
      msg = AAPMessage.parse(buf)
    except InsufficientAAPDataError:
      continue
  return msg

I don't see any bind, and I don't understand why the python code can call "socket.recv", but in my JavaScript code I can't do "netServer.listen". I think it should be the same.


Octaviotastico
  • 124
  • 5
  • 13
  • Just to be sure - when the problem is occuring, verfiy with a utility like `ss -tanp` or `netstat -tanp` (on Linux) that the address:port combination is in TIME_WAIT state. That is the only state where `SO_REUSEADDR` helps. – VPfB Sep 04 '21 at 07:43
  • Your python code seems to __connect__ to localhost:4243 while your nodejs code seems to __listen__ on localhost:4243. This is something completely different what you test here. You simply cannot have two different servers listen on the same ip:port, since in this case it would be unclear which server should get the incoming connection. This would not be a problem if both servers would do the same anyway, but this is not the case in your question where you want to have some requests end up at backend A and others at backend B. – Steffen Ullrich Sep 04 '21 at 07:56
  • @VPfB I edited the question for you to see, when I run lsof – Octaviotastico Sep 04 '21 at 08:41
  • @SteffenUllrich the only thing my python code does, is literally run "`self.socket.recv(1)`" inside a while(1), and it works. I think that is listening to that port. Maybe I'm wrong – Octaviotastico Sep 04 '21 at 08:50
  • 3
    @Octaviotastico: *"I think that is listening to that port."* - No. Listening to a port means to call bind, listen and accept before any kind of recv can be done. What you seem to do is to connect (being a TCP client) not to listen (being a TCP server). – Steffen Ullrich Sep 04 '21 at 09:50
  • @SteffenUllrich Would it be helpful if I edit the question to show you the python server code? – Octaviotastico Sep 04 '21 at 18:17
  • @Octaviotastico: The code you link to looks like a client (connect), not a server (listen). Names like *AAPTCPClient* are likely not given for a server. – Steffen Ullrich Sep 04 '21 at 18:49

2 Answers2

1

There are things to clarify. 1.) The client uses the bind syscall where the kernel selects the source port automatically.

It does so by checking sys local_portrange sysctl settings.

1.) If you want to bind the client to a static source port, be sure to select a TCP port outside the local_portrange range !

2.) You cannot subscribe to event "*", instead you've to subscribe to the event "data" to receive messages.

For best practice you should also subscribe to the "error" event in case of errors !

These links will get you started right away:

How do SO_REUSEADDR and SO_REUSEPORT differ?

https://idea.popcount.org/2014-04-03-bind-before-connect/

So, for all beginners, who want to dig deeper into networking using node.js…

A working server example:

    // Step 0: Create the netServer and the netClient
//

var HOST = 'localhost';
var PORT = 4243;
var AGENT_ID = 'SO_REUSEADDR DEMO';
var net = require('net');

console.log(`[DEBUG] Server will listen to: ${HOST}:${PORT}`);
console.log(`[DEBUG] Server will register with: ${AGENT_ID}`);

const netServer = net.createServer((c) => {
  console.log('[netServer] Client connected');

  c.on('data', (msg) => {
    console.log('[netServer] Received `message`, MSG:', msg.toString());
  });

  c.on('end', () => {
    console.log('client disconnected');
  });

  c.on('error', function (e) {
    console.log('Error: ' + e.code);
  });

  c.write('hello\r\n');
  c.pipe(c);

}).listen({
  host: HOST,
  port: PORT,
  family: 4, // ipv4, same as socket.AF_INET for python
});

// Code copied from nodejs documentation page (doesn't make any difference)
netServer.on('error', function (e) {
   console.log('Error: ' + e.code);
  if (e.code == 'EADDRINUSE') {
    console.log('Address in use, retrying...');
    setTimeout(function () {
      netServer.close();
      netServer.listen(HOST, PORT);
    }, 1000);
  }
  if ( e.code = 'ECONNRESET' ){
    console.log('Connection reset by peer...');
      setTimeout(function () {
        netServer.close();
        netServer.listen(HOST, PORT);
      }, 1000);
  }

});

The Client:

/* Or use this example tcp client written in node.js.  (Originated with
example code from
http://www.hacksparrow.com/tcp-socket-programming-in-node-js.html.) */

var net = require('net');
var HOST = 'localhost';
var PORT = 4243;

var client = new net.Socket();
client.setTimeout(3000);
client.connect(PORT, HOST, function() {
    console.log("Connected to " + client.address().address + " Source Port: " + client.address().port + " Family: " + client.address().family);
    client.write('Hello, server! Love, Client.');
});

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

client.on('error', function(e) {
    console.log('Error: ' + e.code);
});

client.on('timeout', () => {
  console.log('socket timeout');
  client.end();
});

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

Best Hannes

0

Steffen Ullrich was completely right.

In my JavaScript code, I was trying to create a server to listen to the port 4243.
But you don't need to have a server in order to listen to some port, you can listen with a client too! (At least that's what I understood)

You can create a client connection as following:

const netClient = net.createConnection(PORT, HOST, () => {
  console.log('[netClient] Connected');
});

netClient.on('data', (data) => {
  console.log('[netClient] Received data:', data.toString('utf8'));
});

And with "client.on", then you can receive messages as well, as if it were a server.

I hope this is useful to someone else.

Octaviotastico
  • 124
  • 5
  • 13