-
Notifications
You must be signed in to change notification settings - Fork 184
Cloud on Node.js
We'd like to run uProxy cloud servers on Node.js because it will be cheaper and simpler than running inside Firefox on a (headless) X server.
freedom-for-node provides core.rtcpeerconnection with the help of something called wrtc. wrtc
is a thin layer (as are several other WebRTC-on-Node.js-type NPMs) over something called build-webrtc which makes it possible to compile WebRTC for Node.js.
This has enabled uProxy to include two sample apps that run in Node.js:
Great, right? Unfortunately, there are two major problems:
- it's slow as hell: ~25% of Firefox's throughput
- it's affected much more by latency than either Chrome or Firefox - >66% slowdown when that latency rises to ~150ms
The latency issue is doubly puzzling because uProxy made changes to Chrome in 2015 to improve throughput in the presence of high latency between peers - and wrtc
is built from the same source.
-
wrtc
is temperamental: it only supports a couple of architectures out of the box and some patching is required to make it run on Linux and Docker. - Regarding latency, our best theory right now surrounds Node.js' threadpools: https://github.com/uProxy/uproxy/issues/2612#issuecomment-244219541
NOTE: This only works if wrtc
supports your platform out of the box (some older Ubuntu distributions and versions of OSX). See below for how to make a custom wrtc
build.
git clone https://github.com/uProxy/uproxy.git
cd uproxy
yarn
grunt simpleSocks
node build/src/lib/samples/simple-socks-node
Zork powers cloud servers - cool, we built a Docker image and now you can easily run a Node.js-based uProxy cloud server: (add -d localhost
for testing on your workstation)
git clone https://github.com/uProxy/uproxy-docker.git
cd uproxy-docker/testing/run-scripts
./run_cloud.sh -z uproxy/node-stable
TODO: add -z to our installer
Here's how to make a custom wrtc
build which will work on Linux and run on Docker:
TO LAUNCH A DOCKER SESSION IN WHICH TO BUILD WRTC:
docker run --rm -ti -v $PWD:/work -w /work ubuntu:yakkety
apt-get update
apt-get install -y curl
curl -sL https://deb.nodesource.com/setup_6.x | bash -
apt-get install -y nodejs
npm install -g yarn
apt-get install -y make git python binutils g++
ALSO SEEMS LIKE IF YOU ARE RUNNING ON DOCKER YOU NEED:
apt-get install -y alsa-base pulseaudio
##
## build-webrtc
##
git clone https://github.com/markandrus/build-webrtc.git
cd build-webrtc
# PATCH #1 (https://github.com/js-platform/node-webrtc/issues/281#issuecomment-242173952)
# edit jakelib/environment.js and add:
# 'include_pulse_audio=0',
# 'include_internal_video_render=0'
##
## node-webrtc
##
git clone https://github.com/js-platform/node-webrtc.git
cd node-webrtc
export SKIP_DOWNLOAD=true
export BUILD_WEBRTC_DEPENDENCY=/work/build-webrtc/
# PATCH #2
# git fetch origin pull/287/head:fake_audio_device
# git checkout fake_audio_device
# git merge develop
# IF YOU ARE RUNNING AS ROOT:
# echo 'unsafe-perm = true' > /root/.npmrc
npm install
# cleanup:
rm -fR node_modules/build-webrtc third_party
Aaaaaaaaaaaaaaaaand finally:
node examples/ping-pong-test.js
(find more advice on custom builds at https://github.com/js-platform/node-webrtc/wiki/Building)
This pumps 32MB of data through a datachannel. Copy it into examples/
in your node-webrtc
clone.
'use strict';
const webrtc = require('..');
const MB = 32;
const pc1 = new webrtc.RTCPeerConnection();
const pc2 = new webrtc.RTCPeerConnection();
pc1.onicecandidate = (candidate) => {
if (candidate.candidate) {
console.log('pc1 candidate: ' + JSON.stringify(candidate));
pc2.addIceCandidate(candidate.candidate);
}
};
pc2.onicecandidate = (candidate) => {
if (candidate.candidate) {
console.log('pc2 candidate: ' + JSON.stringify(candidate));
pc1.addIceCandidate(candidate.candidate);
}
};
pc2.ondatachannel = (event) => {
console.log('pc2: data channel open');
const dc = event.channel;
let r = 0;
dc.onmessage = (event) => {
r++;
if (r >= 1024 * MB) {
console.log('all received!');
// TODO: signal that we're done
pc1.close();
pc2.close();
}
};
};
// NOTE: must create the datachannel before the offer
const dc = pc1.createDataChannel('test');
dc.onopen = () => {
console.log('pc1: data channel open');
for (let i = 0; i < 1024 * MB; i++) {
const ab = new ArrayBuffer(1500);
dc.send(ab);
}
console.log('all sent!');
};
pc1.createOffer((offer) => {
console.log('offer: ' + JSON.stringify(offer));
pc1.setLocalDescription(offer);
pc2.setRemoteDescription(offer);
pc2.createAnswer((answer) => {
console.log('answer: ' + JSON.stringify(answer));
pc2.setLocalDescription(answer);
pc1.setRemoteDescription(answer);
}, console.error);
}, console.error);
Again, copy these two files into examples/
in your node-webrtc
clone. Run it like so:
mkfifo fifo
node stress-send.js < fifo | /usr/bin/time -f %e node stress-receive.js > fifo
On Docker:
docker run --rm -i --name=stress-send -v ~/wrtc/node-webrtc:/work -w /work node:6 node examples/stress-send.js < fifo | \
/usr/bin/time -f %e \
docker run --rm -i --name=stress-receive -v ~/wrtc/node-webrtc:/work -w /work node:6 node examples/stress-receive.js > fifo
It's a real pain to configure an individual container's network interface; try just configuring the docker0
virtual bridge:
tc qdisc change dev docker0 root netem delay 100ms
tc qdisc show docker0
tc qdisc del dev docker0 root
'use strict';
const readline = require('readline');
const webrtc = require('.././');
const MB = 32;
const pc = new webrtc.RTCPeerConnection();
const reader = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
pc.onicecandidate = (candidate) => {
if (candidate.candidate) {
console.log(JSON.stringify(candidate));
}
};
// NOTE: must create the datachannel before the offer
const dc = pc.createDataChannel('test');
dc.onopen = () => {
console.error('sender: data channel open');
for (let i = 0; i < 1024 * MB; i++) {
const ab = new ArrayBuffer(1500);
dc.send(ab);
}
console.error('all sent!');
pc.close();
};
pc.createOffer((offer) => {
console.log(JSON.stringify(offer));
pc.setLocalDescription(offer);
}, console.error);
reader.on('line', function(line) {
try {
const yo = JSON.parse(line);
if (yo.type === 'icecandidate') {
pc.addIceCandidate(yo.candidate);
} else {
pc.setRemoteDescription(yo);
}
} catch (e) {
console.error('could not parse line: ' + line);
}
});
'use strict';
const readline = require('readline');
const webrtc = require('.././');
const MB = 32;
const pc = new webrtc.RTCPeerConnection();
const reader = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
pc.onicecandidate = (candidate) => {
if (candidate.candidate) {
console.log(JSON.stringify(candidate));
}
};
pc.ondatachannel = (event) => {
console.error('receiver: data channel open');
const dc = event.channel;
let r = 0;
dc.onmessage = (event) => {
r++;
if (r >= 1024 * MB) {
console.error('all received!');
pc.close();
process.exit();
}
};
};
reader.on('line', function(line) {
try {
const yo = JSON.parse(line);
if (yo.type === 'icecandidate') {
pc.addIceCandidate(yo.candidate);
} else {
pc.setRemoteDescription(yo);
pc.createAnswer((answer) => {
console.log(JSON.stringify(answer));
pc.setLocalDescription(answer);
}, console.error);
}
} catch (e) {
console.error('could not parse line: ' + line);
}
});