///
///
import arraybuffers = require('../../../../third_party/uproxy-lib/arraybuffers/arraybuffers');
import socks = require('../../socks-common/socks-headers');
import proxyintegrationtesttypes = require('./proxy-integration-test.types');
import ProxyIntegrationTester = proxyintegrationtesttypes.ProxyIntegrationTester;
import ReceivedDataEvent = proxyintegrationtesttypes.ReceivedDataEvent;
import freedom_types = require('freedom.types');
// Integration test for the whole proxying system.
// The real work is done in the Freedom module which performs each test.
export function socksEchoTestDescription(useChurn:boolean) {
var testStrings = [
'foo',
'bar',
'longer string',
'1',
'that seems like enough'
];
var testerFactoryManager
:freedom_types.FreedomModuleFactoryManager;
var createTestModule = function(denyLocalhost?:boolean)
:ProxyIntegrationTester {
return testerFactoryManager(denyLocalhost, useChurn);
};
beforeEach((done) => {
freedom('files/freedom-module.json', { 'debug': 'debug' })
.then((freedomModuleFactoryManager) => {
testerFactoryManager = freedomModuleFactoryManager;
done();
});
});
afterEach(() => {
expect(testerFactoryManager).not.toBeUndefined();
// Close all created interfaces to the freedom module.
testerFactoryManager.close();
});
it('run a simple echo test', (done) => {
var input = arraybuffers.stringToArrayBuffer('arbitrary test string');
var testModule = createTestModule();
testModule.startEchoServer().then((port:number) => {
return testModule.connect(port);
}).then((connectionId:string) => {
return testModule.echo(connectionId, input);
}).then((output:ArrayBuffer) => {
expect(arraybuffers.byteEquality(input, output)).toBe(true);
}).catch((e:any) => {
expect(e).toBeUndefined();
}).then(done);
});
it('run multiple echo tests in a batch on one connection', (done) => {
var testBuffers = testStrings.map(arraybuffers.stringToArrayBuffer);
var testModule = createTestModule();
testModule.startEchoServer().then((port:number) => {
return testModule.connect(port);
}).then((connectionId:string) => {
return testModule.echoMultiple(connectionId, testBuffers);
}).then((outputs:ArrayBuffer[]) => {
var concatenatedInputs = arraybuffers.concat(testBuffers);
var concatenatedOutputs = arraybuffers.concat(outputs);
var isEqual = arraybuffers.byteEquality(concatenatedInputs, concatenatedOutputs);
expect(isEqual).toBe(true);
}).catch((e:any) => {
expect(e).toBeUndefined();
}).then(done);
});
it('run multiple echo tests in series on one connection', (done) => {
var testBuffers = testStrings.map(arraybuffers.stringToArrayBuffer);
var testModule = createTestModule();
testModule.startEchoServer().then((port:number) => {
return testModule.connect(port);
}).then((connectionId:string) => {
var i = 0;
return new Promise((F, R) => {
var step = () => {
if (i == testBuffers.length) {
F();
return;
}
testModule.echo(connectionId, testBuffers[i])
.then((echo:ArrayBuffer) => {
expect(arraybuffers.byteEquality(testBuffers[i], echo)).toBe(true);
++i;
}).then(step);
};
step();
});
}).catch((e:any) => {
expect(e).toBeUndefined();
}).then(done);
});
it('connect to the same server multiple times in parallel', (done) => {
var testModule = createTestModule();
testModule.startEchoServer().then((port:number) : Promise => {
var promises = testStrings.map((s:string) : Promise => {
var buffer = arraybuffers.stringToArrayBuffer(s);
return testModule.connect(port).then((connectionId:string) => {
return testModule.echo(connectionId, buffer);
}).then((response:ArrayBuffer) => {
expect(arraybuffers.byteEquality(buffer, response)).toBe(true);
});
});
return Promise.all(promises);
}).catch((e:any) => {
expect(e).toBeUndefined();
}).then(done);
});
it('connect to many different servers in parallel', (done) => {
var testModule = createTestModule();
var promises = testStrings.map((s:string) : Promise => {
var buffer = arraybuffers.stringToArrayBuffer(s);
// For each string, start a new echo server with that name, and
// then echo that string from that server.
return testModule.startEchoServer().then((port:number) => {
return testModule.connect(port);
}).then((connectionId:string) => {
return testModule.echo(connectionId, buffer);
}).then((response:ArrayBuffer) => {
expect(arraybuffers.byteEquality(buffer, response)).toBe(true);
});
});
Promise.all(promises).catch((e:any) => {
expect(e).toBeUndefined();
}).then(done);
});
it('run a localhost echo test while localhost is blocked.', (done) => {
// Get a test module that doesn't allow localhost access.
var testModule = createTestModule(true);
testModule.startEchoServer().then((port:number) => {
return testModule.connect(port);
}).then((connectionId:string) => {
// This code should not run, because testModule.connect() should
// reject with a NOT_ALLOWED error.
expect(connectionId).toBeUndefined();
}, (e:any) => {
expect(e.reply).toEqual(socks.Reply.NOT_ALLOWED);
}).then(done);
});
var runUproxyOrg404Test = (testModule:ProxyIntegrationTester,
done:Function) => {
var nonExistentPath = '/noSuchPath';
var input = arraybuffers.stringToArrayBuffer(
'GET ' + nonExistentPath + ' HTTP/1.0\r\n\r\n');
testModule.connect(80, 'uproxy.org').then((connectionId:string) => {
var isDone = false;
var outputString = '';
testModule.on('receivedData', (event:ReceivedDataEvent) => {
if (isDone) {
return;
}
expect(event.connectionId).toEqual(connectionId);
outputString += arraybuffers.arrayBufferToString(event.response);
if (outputString.indexOf('HTTP/1.0 404 Not Found') != -1 &&
outputString.indexOf(nonExistentPath) != -1) {
isDone = true;
done();
}
});
return testModule.sendData(connectionId, input);
}).catch((e:any) => {
expect(e).toBeUndefined();
});
};
it('fetch from non-localhost address', (done) => {
var testModule = createTestModule();
runUproxyOrg404Test(testModule, done);
});
it('fetch from non-localhost address while localhost is blocked.', (done) => {
var testModule = createTestModule(true);
runUproxyOrg404Test(testModule, done);
});
it('do a request that gets blocked, then another that succeeds.', (done) => {
var nonExistentPath = '/noSuchPath';
var input = arraybuffers.stringToArrayBuffer(
'GET ' + nonExistentPath + ' HTTP/1.0\r\n\r\n');
// Get a test module that doesn't allow localhost access.
var testModule = createTestModule(true);
// Try to connect to localhost, and fail
testModule.connect(1023).then((connectionId:string) => {
// This code should not run, because testModule.connect() should
// reject with a NOT_ALLOWED error.
expect(connectionId).toBeUndefined();
}, (e:any) => {
expect(e.reply).toEqual(socks.Reply.NOT_ALLOWED);
}).then(() => {
runUproxyOrg404Test(testModule, done);
});
});
it('run a localhost-resolving DNS name echo test while localhost is blocked.', (done) => {
// Get a test module with one that doesn't allow localhost access.
var testModule = createTestModule(true);
testModule.startEchoServer().then((port:number) => {
return testModule.connect(port, 'www.127.0.0.1.xip.io');
}).then((connectionId:string) => {
// This code should not run, because testModule.connect() should
// reject.
expect(connectionId).toBeUndefined();
}, (e:any) => {
// On many networks, www.127.0.0.1.xip.io is non-resolvable, because
// corporate DNS can drop responses that resolve to local network
// addresses. Accordingly, the error code may either indicate
// a generic failure (if resolution fails) or NOT_ALLOWED if name
// resolution succeeds. However, to avoid portscanning leaks
// (https://github.com/uProxy/uproxy/issues/809) both will be reported
// as FAILURE
expect(e.reply).toEqual(socks.Reply.FAILURE);
}).then(done);
});
it('attempt to connect to a nonexistent echo daemon', (done) => {
var testModule = createTestModule();
// 1023 is a reserved port.
testModule.connect(1023).then((connectionId:string) => {
// This code should not run, because there is no server on this port.
expect(connectionId).toBeUndefined();
}).catch((e:any) => {
expect(e.reply).toEqual(socks.Reply.CONNECTION_REFUSED);
}).then(done);
});
it('attempt to connect to a nonexistent echo daemon while localhost is blocked', (done) => {
var testModule = createTestModule(true);
// 1023 is a reserved port.
testModule.connect(1023).then((connectionId:string) => {
// This code should not run, because localhost is blocked.
expect(connectionId).toBeUndefined();
}).catch((e:any) => {
expect(e.reply).toEqual(socks.Reply.NOT_ALLOWED);
}).then(done);
});
it('attempt to connect to a nonexistent local echo daemon while localhost is blocked as 0.0.0.0', (done) => {
var testModule = createTestModule(true);
// 1023 is a reserved port.
testModule.connect(1023, '0.0.0.0').then((connectionId:string) => {
// This code should not run because the destination is invalid.
expect(connectionId).toBeUndefined();
}).catch((e:any) => {
// TODO: Make this just NOT_ALLOWED once this bug in ipadddr.js is fixed:
// https://github.com/whitequark/ipaddr.js/issues/9
expect([socks.Reply.NOT_ALLOWED, socks.Reply.FAILURE]).toContain(e.reply);
}).then(done);
});
it('attempt to connect to a nonexistent local echo daemon while localhost is blocked as IPv6', (done) => {
var testModule = createTestModule(true);
// 1023 is a reserved port.
testModule.connect(1023, '::1').then((connectionId:string) => {
// This code should not run, because localhost is blocked.
expect(connectionId).toBeUndefined();
}).catch((e:any) => {
expect(e.reply).toEqual(socks.Reply.NOT_ALLOWED);
}).then(done);
});
it('attempt to connect to a local network IP address while it is blocked', (done) => {
var testModule = createTestModule(true);
// 1023 is a reserved port.
testModule.connect(1023, '10.5.5.5').then((connectionId:string) => {
// This code should not run, because local network access is blocked.
expect(connectionId).toBeUndefined();
}).catch((e:any) => {
expect(e.reply).toEqual(socks.Reply.NOT_ALLOWED);
}).then(done);
});
it('connection refused from DNS name', (done) => {
var testModule = createTestModule();
// Many sites (such as uproxy.org) seem to simply ignore SYN packets on
// unmonitored ports, but openbsd.org actually refuses the connection as
// expected.
testModule.connect(1023, 'openbsd.org').then((connectionId:string) => {
// This code should not run, because there is no server on this port.
expect(connectionId).toBeUndefined();
}).catch((e:any) => {
expect(e.reply).toEqual(socks.Reply.CONNECTION_REFUSED);
}).then(done);
});
it('connection refused from DNS name while localhost is blocked', (done) => {
var testModule = createTestModule(true);
// Many sites (such as uproxy.org) seem to simply ignore SYN packets on
// unmonitored ports, but openbsd.org actually refuses the connection as
// expected.
testModule.connect(1023, 'openbsd.org').then((connectionId:string) => {
// This code should not run, because there is no server on this port.
expect(connectionId).toBeUndefined();
}).catch((e:any) => {
// This should be CONNECTION_REFUSED, but since we can't be sure that the
// domain isn't on the local network, and we're concerned about port
// scanning, we return the generic FAILURE code instead.
// See https://github.com/uProxy/uproxy/issues/809.
expect(e.reply).toEqual(socks.Reply.FAILURE);
}).then(done);
});
// Disabled because HOST_UNREACHABLE is not yet exposed in freedom-for-chrome's
// implementation of the core.tcpsocket API.
// https://github.com/freedomjs/freedom-for-chrome/issues/73
xit('attempt to connect to a nonexistent DNS name', (done) => {
var testModule = createTestModule(true);
testModule.connect(80, 'www.nonexistentdomain.gov').then((connectionId:string) => {
// This code should not run, because there is no such DNS name.
expect(connectionId).toBeUndefined();
}).catch((e:any) => {
expect(e.reply).toEqual(socks.Reply.HOST_UNREACHABLE);
}).then(done);
});
};