2

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.

JohnH
  • 2,713
  • 12
  • 21
K Lovell
  • 45
  • 7
  • I'm having trouble understanding what the flow looks like here, and what you are trying to achieve. It sounds like you want to go from MAX -> Node.js Server 1 -> Node.js Server 2 -> [several MAX clients]. Is that correct? If not, can you draw a diagram? And, why not just handle this at the network layer and forward UDP packets? You might even be able to use an IP multicast group without any intermediary at all. – Brad Oct 19 '17 at 05:21
  • It goes from MAX -> Node.js server -> serveral max clients running the second Node.js script. The second script is meant to act like UDP forwarding, sending information that the computer receives at its public ip to its private network. I'd much rather allow UDP forwarding without the second script, but I couldn't find a way to configure it on plain Windows. Figured the script could do it, but I guess it doesn't. If you know how to set it up, I'd love to know. – K Lovell Oct 19 '17 at 09:34
  • All of the guides I've found for UDP forwarding on Windows seem to be only for Windows Server, and I'm trying this simply with desktop Windows for the end machines. – K Lovell Oct 19 '17 at 09:40
  • I like your ingenuity. Unfortunately, I'm finding it difficult to follow your narrative as written. The point of the last script is to listen on external and forward to a service that only listens in 127.0.0.1 and thus, directly, is externally inaccessible... is that right? But this part isn't the problem... the problem is that this script receives no traffic? – Michael - sqlbot Oct 19 '17 at 11:32
  • If so, you might use `tshark` on the EC2 side to verify that you really are sending what you believe you are sending, and Wireshark on the receive machines to see what arrives. These are indispensable for troubleshooting network things. But also, these target machines... how are they connected to the Internet? Do they all have public IP addresses bound directly to their stacks? – Michael - sqlbot Oct 19 '17 at 11:35
  • Hi Kathryn, let me try you code sample and I'll be back to you. – flapjack Oct 20 '17 at 03:28

2 Answers2

1

Daniel's solution to use WebSockets is correct. However, I also needed to be sure to connect the clients via the DNS name rather than the public ip address. For example:

const WebSocketClient = require('websocket').client;
const port = process.env.PORT || 3000;
const wsc = new WebSocketClient();
wsc.connect('ws://ec2.compute-1.amazonaws.com:4000/', 'echo-protocol');

As opposed to:

wsc.connect('ws://1.2.3.4:4000/', 'echo-protocol');

More details about this problem here.

I also used the 'websocket' npm module instead of 'ws,' but I'm not sure that made a difference.

K Lovell
  • 45
  • 7
0

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.

flapjack
  • 704
  • 1
  • 8
  • 13
  • Hi Dan, thanks for your reply. I tried your code yesterday, and despite the massive improvement in quality, unfortunately it's still failing in a similar way. I believe it may have to to with Windows subnet masking, and tried this solution. [link](https://stackoverflow.com/questions/6177423/send-broadcast-datagram) Can I ask why there is no bind() call in your client code? – K Lovell Oct 21 '17 at 17:19
  • There's currently no way to tell if this "broadcast address" path is the right one besides if it works or if it doesn't. I think I may try wireshark next. – K Lovell Oct 21 '17 at 17:25