Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/lib/integration-tests/socks-echo/base-spec.core-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,4 +409,18 @@ export function socksEchoTestDescription(useChurn:boolean) {
expect(e.reply).toEqual(socks_headers.Reply.HOST_UNREACHABLE);
}).then(done);
});

it('run a simple echo test using SOCKS 4', (done) => {
var input = arraybuffers.stringToArrayBuffer('arbitrary test string');
var testModule = createTestModule();
testModule.startEchoServer().then((port:number) => {
return testModule.connect(port, '127.0.0.1', /* useV4 */ true);
}).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);
});
};
2 changes: 1 addition & 1 deletion src/lib/integration-tests/socks-echo/freedom-module.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"connect": {
"type": "method",
"value": ["number", "string"],
"value": ["number", "string", "boolean"],
"ret": "string"
},
"setRepeat": {
Expand Down
36 changes: 35 additions & 1 deletion src/lib/integration-tests/socks-echo/proxy-integration-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,37 @@ export default class AbstractProxyIntegrationTest implements ProxyIntegrationTes
});
}

private connectThroughSocksV4_ = (socksEndpoint:net.Endpoint, webEndpoint:net.Endpoint) : Promise<tcp.Connection> => {
var connection = new tcp.Connection({endpoint: socksEndpoint});
connection.onceClosed.then(() => {
console.log('Socket ' + connection.connectionId + ' has closed');
this.dispatchEvent_('sockClosed', connection.connectionId);
});

let request :socks_headers.Request = {
command: socks_headers.Command.TCP_CONNECT,
endpoint: webEndpoint,
};
connection.send(socks_headers.composeRequestBufferV4(request));
var connected = new Promise<tcp.ConnectionInfo>((F, R) => {
connection.onceConnected.then(F);
connection.onceClosed.then(R);
});
var firstBufferPromise :Promise<ArrayBuffer> = connection.receiveNext();
return connected.then((i:tcp.ConnectionInfo) => {
return firstBufferPromise;
}).then((buffer:ArrayBuffer) : Promise<tcp.Connection> => {
let response = socks_headers.interpretResponseBufferV4(buffer);
log.debug('Received request response: %1', [response]);
if (response.reply !== socks_headers.Reply.SUCCEEDED) {
// TODO: Fix bad style: reject should only and always be an error.
// We should be resolving with result status.
return Promise.reject(response);
}
return Promise.resolve(connection);
});
}

private connectThroughSocks_ = (socksEndpoint:net.Endpoint, webEndpoint:net.Endpoint) : Promise<tcp.Connection> => {
var connection = new tcp.Connection({endpoint: socksEndpoint});
connection.onceClosed.then(() => {
Expand Down Expand Up @@ -246,13 +277,16 @@ export default class AbstractProxyIntegrationTest implements ProxyIntegrationTes
});
}

public connect = (port:number, address?:string) : Promise<string> => {
public connect = (port:number, address?:string, useV4?:boolean) : Promise<string> => {
try {
return this.socksEndpoint_.then((socksEndpoint:net.Endpoint) : Promise<tcp.Connection> => {
var echoEndpoint :net.Endpoint = {
address: address || this.localhost_,
port: port
};
if (useV4) {
return this.connectThroughSocksV4_(socksEndpoint, echoEndpoint)
}
return this.connectThroughSocks_(socksEndpoint, echoEndpoint);
}).then((connection:tcp.Connection) => {
this.connections_[connection.connectionId] = connection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface ReceivedDataEvent {
export interface ProxyIntegrationTester {
startEchoServer() :Promise<number>;
// Returns a unique identifier for the connection (the connectionId).
connect(port:number, address?:string) :Promise<string>;
connect(port:number, address?:string, useV4?:boolean) :Promise<string>;
// Sets the number of concatenated copies of the input to echo. (default: 1)
setRepeat(repeat:number) :Promise<void>;
echo(connectionId:string, content:ArrayBuffer) :Promise<ArrayBuffer>;
Expand Down
60 changes: 39 additions & 21 deletions src/lib/socks-to-rtc/socks-to-rtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,7 @@ export class Session {

// The session is ready once we've completed both
// auth and request handshakes.
this.onceReady = this.doAuthHandshake_().then(
this.doRequestHandshake_).then((response:socks_headers.Response) => {
this.onceReady = this.doHandshake_().then((response:socks_headers.Response) => {
if (response.reply !== socks_headers.Reply.SUCCEEDED) {
throw new Error('handshake failed with reply code ' +
socks_headers.Reply[response.reply]);
Expand Down Expand Up @@ -300,20 +299,29 @@ export class Session {
});
}

private doHandshake_ = () : Promise<socks_headers.Response> => {
return this.tcpConnection_.receiveNext().then((firstBuffer:ArrayBuffer) => {
let version = socks_headers.checkVersion(firstBuffer);
if (version === socks_headers.Version.VERSION5) {
return this.doAuthHandshake_(firstBuffer).then(this.doRequestHandshake_);
} else if (version === socks_headers.Version.VERSION4) {
return this.doV4Handshake_(firstBuffer);
} else {
throw new Error('Invalid SOCKS version ' + version);
}
});
}

// Receive a socks connection and send the initial Auth messages.
// Assumes: no packet fragmentation.
// TODO: send failure to client if auth fails
// TODO: handle packet fragmentation:
// https://github.com/uProxy/uproxy/issues/323
// TODO: Needs unit tests badly since it's mocked by several other tests.
private doAuthHandshake_ = ()
: Promise<void> => {
return this.tcpConnection_.receiveNext()
.then(socks_headers.interpretAuthHandshakeBuffer)
.then((auths:socks_headers.Auth[]) => {
this.tcpConnection_.send(
private doAuthHandshake_ = (authRequestBuffer:ArrayBuffer) : Promise<void> => {
let auths:socks_headers.Auth[] = socks_headers.interpretAuthHandshakeBuffer(authRequestBuffer);
return this.tcpConnection_.send(
socks_headers.composeAuthResponse(socks_headers.Auth.NOAUTH));
});
}

// Handles the SOCKS handshake, fulfilling with the socks.Response instance
Expand All @@ -332,14 +340,20 @@ export class Session {
private doRequestHandshake_ = () : Promise<socks_headers.Response> => {
return this.tcpConnection_.receiveNext()
.then(socks_headers.interpretRequestBuffer)
.then((request:socks_headers.Request) => {
// The domain name is very sensitive, so we keep it out of the
// info-level logs, which may be uploaded.
log.debug('%1: received endpoint from SOCKS client: %2', [
this.longId(), JSON.stringify(request.endpoint)]);
this.tcpConnection_.pause();
return this.dataChannel_.send({ str: JSON.stringify(request) });
})
.then(this.handshakeWithPeer_)
.then((response:socks_headers.Response) => {
return this.tcpConnection_.send(socks_headers.composeResponseBuffer(
response)).then((discard:any) => { return response; });
});
}

private handshakeWithPeer_ = (request:socks_headers.Request) : Promise<socks_headers.Response> => {
// The domain name is very sensitive, so we keep it out of the
// info-level logs, which may be uploaded.
log.debug('%1: received endpoint from SOCKS client: %2', [
this.longId(), JSON.stringify(request.endpoint)]);
this.tcpConnection_.pause();
return this.dataChannel_.send({ str: JSON.stringify(request) })
.then(() => {
// Equivalent to channel.receiveNext(), if it existed.
return new Promise((F, R) => {
Expand Down Expand Up @@ -370,13 +384,17 @@ export class Session {
return {
reply: socks_headers.Reply.FAILURE
};
})
.then((response:socks_headers.Response) => {
return this.tcpConnection_.send(socks_headers.composeResponseBuffer(
response)).then((discard:any) => { return response; });
});
}

private doV4Handshake_ = (buffer:ArrayBuffer) : Promise<socks_headers.Response> => {
let request = socks_headers.interpretRequestBufferV4(buffer);
return this.handshakeWithPeer_(request).then((response:socks_headers.Response) => {
return this.tcpConnection_.send(socks_headers.composeResponseBufferV4(
response)).then((discard:any) => { return response; });
});
}

// Sends a packet over the data channel.
// Invoked when a packet is received over the TCP socket.
private sendOnChannel_ = (data:ArrayBuffer) : void => {
Expand Down
83 changes: 83 additions & 0 deletions src/lib/socks/headers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,87 @@ describe('socks', function() {
udpMessageArray[2] = 7;
expect(() => { Socks.interpretUdpMessage(udpMessageArray); }).toThrow();
});

it('roundtrip SOCKS 4 request', () => {
let request : Socks.Request = {
command: Socks.Command.TCP_CONNECT,
endpoint: {
address: '192.0.2.4',
port: 54321
}
};
let requestBuffer = Socks.composeRequestBufferV4(request);
let requestArray = new Uint8Array(requestBuffer);
expect(requestArray[0]).toEqual(Socks.Version.VERSION4);
expect(requestArray[1]).toEqual(Socks.Command.TCP_CONNECT);
expect(requestArray[2] << 8 | requestArray[3]).toEqual(request.endpoint.port);
expect(requestArray[8]).toEqual(0);
expect(requestArray.length).toEqual(9);
let requestAgain = Socks.interpretRequestBufferV4(requestBuffer);
expect(requestAgain).toEqual(request);
});

it('roundtrip SOCKS 4a request', () => {
let request : Socks.Request = {
command: Socks.Command.TCP_CONNECT,
endpoint: {
address: 'www.example.com',
port: 1200
}
};
let requestBuffer = Socks.composeRequestBufferV4(request);
let requestArray = new Uint8Array(requestBuffer);
expect(requestArray[0]).toEqual(Socks.Version.VERSION4);
expect(requestArray[1]).toEqual(Socks.Command.TCP_CONNECT);
expect(requestArray[2] << 8 | requestArray[3]).toEqual(request.endpoint.port);
expect(requestArray[4] | requestArray[5] | requestArray[6]).toEqual(0);
expect(requestArray[7]).toEqual(request.endpoint.address.length);
expect(requestArray[8]).toEqual(0);
expect(requestArray.length).toEqual(10 + request.endpoint.address.length);
let requestAgain = Socks.interpretRequestBufferV4(requestBuffer);
expect(requestAgain).toEqual(request);
});

it('check SOCKS 4 version', () => {
let request : Socks.Request = {
command: Socks.Command.TCP_CONNECT,
endpoint: {
address: '192.0.2.4',
port: 54321
}
};
let requestBuffer = Socks.composeRequestBufferV4(request);
let version = Socks.checkVersion(requestBuffer);
expect(version).toEqual(Socks.Version.VERSION4);
});

it('check SOCKS 5 version', () => {
let request : Socks.Request = {
command: Socks.Command.TCP_CONNECT,
endpoint: {
address: '192.0.2.4',
port: 54321
}
};
let requestBuffer = Socks.composeRequestBuffer(request);
let version = Socks.checkVersion(requestBuffer);
expect(version).toEqual(Socks.Version.VERSION5);
});

it('roundtrip IPv4 response', () => {
let response :Socks.Response = {
reply: Socks.Reply.SUCCEEDED,
endpoint: {
address: '255.0.1.77',
port: 65535
}
};
let responseBuffer = Socks.composeResponseBufferV4(response);
let responseArray = new Uint8Array(responseBuffer);
expect(responseArray[0]).toEqual(Socks.Version.VERSION0);
expect(responseArray[1]).toEqual(Socks.ResultCode4.REQUEST_GRANTED);
expect(responseArray[2] << 8 | responseArray[3]).toEqual(response.endpoint.port);
let responseAgain = Socks.interpretResponseBufferV4(responseBuffer);
expect(responseAgain).toEqual(response);
});
});
Loading