node.jsamazon-ec2udposcmax-msp-jitter

Passing OSC messages between two computers over WAN via UDP and Node.js


I'm trying to write some minimalist client-server code to pass OSC messages between two computers that are on different local networks. Specifically my end goal is to send and receive from MAX patches in Ableton, using MAX's UDPSend and UDPReceive functions, which simply listen on local ports. Here is the server code, that I have Ableton's OSC Send MIDI module send to. This correctly receives output from the first computer's MAX patch, some MIDI data from a channel.

var osc = require('osc-min'),
dgram = require('dgram');
var remotes = [];
var FileReader = require('fs');

// listen for OSC messages and print them to the console
var udp = dgram.createSocket('udp4', function(msg, rinfo) {
  try {
    var oscmsg = osc.fromBuffer(msg);
    console.log(oscmsg);
    remotes.forEach(function(remote, index, array) {
        udp.send(msg, 0, oscmsg.length, 10000, remote);
        console.log('Sent OSC message to %s:%d', remote, 10000);
    });
  } catch (err) {
    console.log(err);
  }

});

FileReader.readFile('ips.txt', 'utf8', function(err, data) {
    // By lines
    var lines = data.split('\n');
    for(var line = 0; line < lines.length; line++){
        remotes.push(lines[line]);
    }
});

udp.bind(9998);
console.log('Listening for OSC messages on port 9998');

The server correctly gets an OSC message like this:

{ address: '/Note1',
args: [ { type: 'integer', value: 60 } ],
oscType: 'message' }

I then want to relay Computer 1's message to my remote server on port 9998 to the rest of the computers I have listening on port 10000. (The server is an Amazon EC2 Ubuntu instance.) The public IPs of these computers are in a file ips.txt.

On the receiving computers (testing on Windows), I set up a Node client to receive data via its public IP and relay it to localhost so the MAX patch can see it. Thus, any messages sent to 10000 on this computer will be sent to 127.0.0.1:9999.

var osc = require('osc-min'),
    dgram = require('dgram'),
    remote;

// listen for OSC messages and print them to the console
var udp = dgram.createSocket('udp4', function(msg, rinfo) {

  try {
    var oscmsg = osc.fromBuffer(msg);
    console.log(oscmsg);
    send(msg);
  } catch (err) {
    console.log('Could not decode OSC message');
  }

});

function send(oscMessage) {

  udp.send(oscMessage, 0, oscMessage.length, 9999, "127.0.0.1");
  console.log('Sent OSC message to %s:%d', "127.0.0.1", 9999);

}    
udp.bind(10000);
console.log('Listening for OSC messages on port 10000');

The problem is, the server's message never seems to reach the client. Once the server receives the message, it produces output as such:

Listening for OSC messages on port 9998
{ address: '/Velocity1',
args: [ { type: 'integer', value: 100 } ],   oscType: 'message' }
Sent OSC message to client-public-ip:10000

But the client simply hangs on it's initial listening state

Listening for OSC messages on port 10000

I thought this may be a problem with my firewall, and I made sure to enable UDP specifically through the port and on all the applications involved, to no gain. I'm hoping to avoid using HTTPRequests. I'd greatly appreciate any suggestions.

Edit: I created a diagram to help visualize what's supposed to be going on between these three computers. The dashed boxes represent the local network that computer is on (they are all on different networks). I hope this helps a bit.


Solution

  • The easiest way I've found to get this working between different LANs and WAN was to proxy the UDP requests and send them to an Echo server using Websockets. Here is an example:

    Echo Server (WAN)

    "use strict";
    
    const WebSocket = require('ws');
    const port = process.env.PORT || 3000;
    const wss = new WebSocket.Server({ port: port });
    
    // Broadcast to all
    wss.broadcast = function broadcast(data) {
        wss.clients.forEach(function each(client) {
            if (client.readyState === WebSocket.OPEN) {
                client.send(data);
            }
        });
    };
    
    wss.on('connection', function connection(ws, req) {
        const ip = req.connection.remoteAddress;
        console.log('connecting to', ip);
    
        ws.on('message', function incoming(data) {
            // Broadcast to everyone else.
            wss.clients.forEach(function each(client) {
                if (client !== ws && client.readyState === WebSocket.OPEN) {
                    client.send(data);
                }
            });
        });
    });
    

    UDP Server / WebSocket Client (Local)

    const Buffer = require('buffer').Buffer;
    const dgram = require('dgram');
    const WebSocketServer = require('ws').Server;
    
    const port = process.env.PORT || 3000;
    
    const wss = new WebSocketServer({port});
    
    //The ip port of the UDP server
    var SERVER_IP = '127.0.0.1'
    var SERVER_PORT = 11000
    
    wss.on('connection', function(ws) {
        //Create a udp socket for this websocket connection
        let udpClient = dgram.createSocket('udp4');
    
        //When a message is received from udp server send it to the ws client
        udpClient.on('message', function(msg, rinfo) {
            ws.send(msg.toString());
        });
    
        //When a message is received from ws client send it to UDP server.
        ws.on('message', function(message) {
            var msgBuff = new Buffer(message);
            udpClient.send(msgBuff, 0, msgBuff.length, SERVER_PORT, SERVER_IP);
        });
    });
    

    Testing

    You can configure Ableton connecting to the local UDP server, in this example de default address would be something like udp://localhost:11000.