1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/test/unit/test_socks.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,531 @@ 1.4 +const CC = Components.Constructor; 1.5 + 1.6 +const ServerSocket = CC("@mozilla.org/network/server-socket;1", 1.7 + "nsIServerSocket", 1.8 + "init"); 1.9 +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", 1.10 + "nsIBinaryInputStream", 1.11 + "setInputStream"); 1.12 +const DirectoryService = CC("@mozilla.org/file/directory_service;1", 1.13 + "nsIProperties"); 1.14 +const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init"); 1.15 + 1.16 +const currentThread = Cc["@mozilla.org/thread-manager;1"] 1.17 + .getService().currentThread; 1.18 + 1.19 +var socks_test_server = null; 1.20 +var socks_listen_port = -1; 1.21 + 1.22 +function getAvailableBytes(input) 1.23 +{ 1.24 + var len = 0; 1.25 + 1.26 + try { 1.27 + len = input.available(); 1.28 + } catch (e) { 1.29 + } 1.30 + 1.31 + return len; 1.32 +} 1.33 + 1.34 +function runScriptSubprocess(script, args) 1.35 +{ 1.36 + // logic copied from ted's crashreporter unit test 1.37 + var ds = new DirectoryService(); 1.38 + var bin = ds.get("CurProcD", Ci.nsILocalFile); 1.39 + 1.40 + bin.append("xpcshell"); 1.41 + if (!bin.exists()) { 1.42 + bin.leafName = "xpcshell.exe"; 1.43 + do_check_true(bin.exists()); 1.44 + if (!bin.exists()) 1.45 + do_throw("Can't find xpcshell binary"); 1.46 + } 1.47 + 1.48 + var script = do_get_file(script); 1.49 + var proc = new Process(bin); 1.50 + var args = [script.path].concat(args); 1.51 + 1.52 + proc.run(false, args, args.length); 1.53 + 1.54 + return proc; 1.55 +} 1.56 + 1.57 +function buf2ip(buf) 1.58 +{ 1.59 + if (buf.length == 16) { 1.60 + var ip = (buf[0] << 4 | buf[1]).toString(16) + ':' + 1.61 + (buf[2] << 4 | buf[3]).toString(16) + ':' + 1.62 + (buf[4] << 4 | buf[5]).toString(16) + ':' + 1.63 + (buf[6] << 4 | buf[7]).toString(16) + ':' + 1.64 + (buf[8] << 4 | buf[9]).toString(16) + ':' + 1.65 + (buf[10] << 4 | buf[11]).toString(16) + ':' + 1.66 + (buf[12] << 4 | buf[13]).toString(16) + ':' + 1.67 + (buf[14] << 4 | buf[15]).toString(16); 1.68 + for (var i = 8; i >= 2; i--) { 1.69 + var re = new RegExp("(^|:)(0(:|$)){" + i + "}"); 1.70 + var shortip = ip.replace(re, '::'); 1.71 + if (shortip != ip) { 1.72 + return shortip; 1.73 + } 1.74 + } 1.75 + return ip; 1.76 + } else { 1.77 + return buf.join('.'); 1.78 + } 1.79 +} 1.80 + 1.81 +function buf2int(buf) 1.82 +{ 1.83 + var n = 0; 1.84 + 1.85 + for (var i in buf) { 1.86 + n |= buf[i] << ((buf.length - i - 1) * 8); 1.87 + } 1.88 + 1.89 + return n; 1.90 +} 1.91 + 1.92 +function buf2str(buf) 1.93 +{ 1.94 + return String.fromCharCode.apply(null, buf); 1.95 +} 1.96 + 1.97 +const STATE_WAIT_GREETING = 1; 1.98 +const STATE_WAIT_SOCKS4_REQUEST = 2; 1.99 +const STATE_WAIT_SOCKS4_USERNAME = 3; 1.100 +const STATE_WAIT_SOCKS4_HOSTNAME = 4; 1.101 +const STATE_WAIT_SOCKS5_GREETING = 5; 1.102 +const STATE_WAIT_SOCKS5_REQUEST = 6; 1.103 +const STATE_WAIT_PONG = 7; 1.104 +const STATE_GOT_PONG = 8; 1.105 + 1.106 +function SocksClient(server, client_in, client_out) 1.107 +{ 1.108 + this.server = server; 1.109 + this.type = ''; 1.110 + this.username = ''; 1.111 + this.dest_name = ''; 1.112 + this.dest_addr = []; 1.113 + this.dest_port = []; 1.114 + 1.115 + this.client_in = client_in; 1.116 + this.client_out = client_out; 1.117 + this.inbuf = []; 1.118 + this.outbuf = String(); 1.119 + this.state = STATE_WAIT_GREETING; 1.120 + this.waitRead(this.client_in); 1.121 +} 1.122 +SocksClient.prototype = { 1.123 + onInputStreamReady: function(input) 1.124 + { 1.125 + var len = getAvailableBytes(input); 1.126 + 1.127 + if (len == 0) { 1.128 + print('server: client closed!'); 1.129 + do_check_eq(this.state, STATE_GOT_PONG); 1.130 + this.server.testCompleted(this); 1.131 + return; 1.132 + } 1.133 + 1.134 + var bin = new BinaryInputStream(input); 1.135 + var data = bin.readByteArray(len); 1.136 + this.inbuf = this.inbuf.concat(data); 1.137 + 1.138 + switch (this.state) { 1.139 + case STATE_WAIT_GREETING: 1.140 + this.checkSocksGreeting(); 1.141 + break; 1.142 + case STATE_WAIT_SOCKS4_REQUEST: 1.143 + this.checkSocks4Request(); 1.144 + break; 1.145 + case STATE_WAIT_SOCKS4_USERNAME: 1.146 + this.checkSocks4Username(); 1.147 + break; 1.148 + case STATE_WAIT_SOCKS4_HOSTNAME: 1.149 + this.checkSocks4Hostname(); 1.150 + break; 1.151 + case STATE_WAIT_SOCKS5_GREETING: 1.152 + this.checkSocks5Greeting(); 1.153 + break; 1.154 + case STATE_WAIT_SOCKS5_REQUEST: 1.155 + this.checkSocks5Request(); 1.156 + break; 1.157 + case STATE_WAIT_PONG: 1.158 + this.checkPong(); 1.159 + break; 1.160 + default: 1.161 + do_throw("server: read in invalid state!"); 1.162 + } 1.163 + 1.164 + this.waitRead(input); 1.165 + }, 1.166 + 1.167 + onOutputStreamReady: function(output) 1.168 + { 1.169 + var len = output.write(this.outbuf, this.outbuf.length); 1.170 + if (len != this.outbuf.length) { 1.171 + this.outbuf = this.outbuf.substring(len); 1.172 + this.waitWrite(output); 1.173 + } else 1.174 + this.outbuf = String(); 1.175 + }, 1.176 + 1.177 + waitRead: function(input) 1.178 + { 1.179 + input.asyncWait(this, 0, 0, currentThread); 1.180 + }, 1.181 + 1.182 + waitWrite: function(output) 1.183 + { 1.184 + output.asyncWait(this, 0, 0, currentThread); 1.185 + }, 1.186 + 1.187 + write: function(buf) 1.188 + { 1.189 + this.outbuf += buf; 1.190 + this.waitWrite(this.client_out); 1.191 + }, 1.192 + 1.193 + checkSocksGreeting: function() 1.194 + { 1.195 + if (this.inbuf.length == 0) 1.196 + return; 1.197 + 1.198 + if (this.inbuf[0] == 4) { 1.199 + print('server: got socks 4'); 1.200 + this.type = 'socks4'; 1.201 + this.state = STATE_WAIT_SOCKS4_REQUEST; 1.202 + this.checkSocks4Request(); 1.203 + } else if (this.inbuf[0] == 5) { 1.204 + print('server: got socks 5'); 1.205 + this.type = 'socks'; 1.206 + this.state = STATE_WAIT_SOCKS5_GREETING; 1.207 + this.checkSocks5Greeting(); 1.208 + } else { 1.209 + do_throw("Unknown socks protocol!"); 1.210 + } 1.211 + }, 1.212 + 1.213 + checkSocks4Request: function() 1.214 + { 1.215 + if (this.inbuf.length < 8) 1.216 + return; 1.217 + 1.218 + do_check_eq(this.inbuf[1], 0x01); 1.219 + 1.220 + this.dest_port = this.inbuf.slice(2, 4); 1.221 + this.dest_addr = this.inbuf.slice(4, 8); 1.222 + 1.223 + this.inbuf = this.inbuf.slice(8); 1.224 + this.state = STATE_WAIT_SOCKS4_USERNAME; 1.225 + this.checkSocks4Username(); 1.226 + }, 1.227 + 1.228 + readString: function() 1.229 + { 1.230 + var i = this.inbuf.indexOf(0); 1.231 + var str = null; 1.232 + 1.233 + if (i >= 0) { 1.234 + var buf = this.inbuf.slice(0,i); 1.235 + str = buf2str(buf); 1.236 + this.inbuf = this.inbuf.slice(i+1); 1.237 + } 1.238 + 1.239 + return str; 1.240 + }, 1.241 + 1.242 + checkSocks4Username: function() 1.243 + { 1.244 + var str = this.readString(); 1.245 + 1.246 + if (str == null) 1.247 + return; 1.248 + 1.249 + this.username = str; 1.250 + if (this.dest_addr[0] == 0 && 1.251 + this.dest_addr[1] == 0 && 1.252 + this.dest_addr[2] == 0 && 1.253 + this.dest_addr[3] != 0) { 1.254 + this.state = STATE_WAIT_SOCKS4_HOSTNAME; 1.255 + this.checkSocks4Hostname(); 1.256 + } else { 1.257 + this.sendSocks4Response(); 1.258 + } 1.259 + }, 1.260 + 1.261 + checkSocks4Hostname: function() 1.262 + { 1.263 + var str = this.readString(); 1.264 + 1.265 + if (str == null) 1.266 + return; 1.267 + 1.268 + this.dest_name = str; 1.269 + this.sendSocks4Response(); 1.270 + }, 1.271 + 1.272 + sendSocks4Response: function() 1.273 + { 1.274 + this.outbuf = '\x00\x5a\x00\x00\x00\x00\x00\x00'; 1.275 + this.sendPing(); 1.276 + }, 1.277 + 1.278 + checkSocks5Greeting: function() 1.279 + { 1.280 + if (this.inbuf.length < 2) 1.281 + return; 1.282 + var nmethods = this.inbuf[1]; 1.283 + if (this.inbuf.length < 2 + nmethods) 1.284 + return; 1.285 + 1.286 + do_check_true(nmethods >= 1); 1.287 + var methods = this.inbuf.slice(2, 2 + nmethods); 1.288 + do_check_true(0 in methods); 1.289 + 1.290 + this.inbuf = []; 1.291 + this.state = STATE_WAIT_SOCKS5_REQUEST; 1.292 + this.write('\x05\x00'); 1.293 + }, 1.294 + 1.295 + checkSocks5Request: function() 1.296 + { 1.297 + if (this.inbuf.length < 4) 1.298 + return; 1.299 + 1.300 + do_check_eq(this.inbuf[0], 0x05); 1.301 + do_check_eq(this.inbuf[1], 0x01); 1.302 + do_check_eq(this.inbuf[2], 0x00); 1.303 + 1.304 + var atype = this.inbuf[3]; 1.305 + var len; 1.306 + var name = false; 1.307 + 1.308 + switch (atype) { 1.309 + case 0x01: 1.310 + len = 4; 1.311 + break; 1.312 + case 0x03: 1.313 + len = this.inbuf[4]; 1.314 + name = true; 1.315 + break; 1.316 + case 0x04: 1.317 + len = 16; 1.318 + break; 1.319 + default: 1.320 + do_throw("Unknown address type " + atype); 1.321 + } 1.322 + 1.323 + if (name) { 1.324 + if (this.inbuf.length < 4 + len + 1 + 2) 1.325 + return; 1.326 + 1.327 + buf = this.inbuf.slice(5, 5 + len); 1.328 + this.dest_name = buf2str(buf); 1.329 + len += 1; 1.330 + } else { 1.331 + if (this.inbuf.length < 4 + len + 2) 1.332 + return; 1.333 + 1.334 + this.dest_addr = this.inbuf.slice(4, 4 + len); 1.335 + } 1.336 + 1.337 + len += 4; 1.338 + this.dest_port = this.inbuf.slice(len, len + 2); 1.339 + this.inbuf = this.inbuf.slice(len + 2); 1.340 + this.sendSocks5Response(); 1.341 + }, 1.342 + 1.343 + sendSocks5Response: function() 1.344 + { 1.345 + if (this.dest_addr.length == 16) { 1.346 + // send a successful response with the address, [::1]:80 1.347 + this.outbuf += '\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80'; 1.348 + } else { 1.349 + // send a successful response with the address, 127.0.0.1:80 1.350 + this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80'; 1.351 + } 1.352 + this.sendPing(); 1.353 + }, 1.354 + 1.355 + sendPing: function() 1.356 + { 1.357 + print('server: sending ping'); 1.358 + this.state = STATE_WAIT_PONG; 1.359 + this.outbuf += "PING!"; 1.360 + this.inbuf = []; 1.361 + this.waitWrite(this.client_out); 1.362 + this.waitRead(this.client_in); 1.363 + }, 1.364 + 1.365 + checkPong: function() 1.366 + { 1.367 + var pong = buf2str(this.inbuf); 1.368 + do_check_eq(pong, "PONG!"); 1.369 + this.state = STATE_GOT_PONG; 1.370 + this.waitRead(this.client_in); 1.371 + }, 1.372 + 1.373 + close: function() 1.374 + { 1.375 + this.client_in.close(); 1.376 + this.client_out.close(); 1.377 + } 1.378 +}; 1.379 + 1.380 +function SocksTestServer() 1.381 +{ 1.382 + this.listener = ServerSocket(-1, true, -1); 1.383 + socks_listen_port = this.listener.port; 1.384 + print('server: listening on', socks_listen_port); 1.385 + this.listener.asyncListen(this); 1.386 + this.test_cases = []; 1.387 + this.client_connections = []; 1.388 + this.client_subprocess = null; 1.389 + // port is used as the ID for test cases 1.390 + this.test_port_id = 8000; 1.391 + this.tests_completed = 0; 1.392 +} 1.393 +SocksTestServer.prototype = { 1.394 + addTestCase: function(test) 1.395 + { 1.396 + test.finished = false; 1.397 + test.port = this.test_port_id++; 1.398 + this.test_cases.push(test); 1.399 + }, 1.400 + 1.401 + pickTest: function(id) 1.402 + { 1.403 + for (var i in this.test_cases) { 1.404 + var test = this.test_cases[i]; 1.405 + if (test.port == id) { 1.406 + this.tests_completed++; 1.407 + return test; 1.408 + } 1.409 + } 1.410 + do_throw("No test case with id " + id); 1.411 + }, 1.412 + 1.413 + testCompleted: function(client) 1.414 + { 1.415 + var port_id = buf2int(client.dest_port); 1.416 + var test = this.pickTest(port_id); 1.417 + 1.418 + print('server: test finished', test.port); 1.419 + do_check_true(test != null); 1.420 + do_check_eq(test.expectedType || test.type, client.type); 1.421 + do_check_eq(test.port, port_id); 1.422 + 1.423 + if (test.remote_dns) 1.424 + do_check_eq(test.host, client.dest_name); 1.425 + else 1.426 + do_check_eq(test.host, buf2ip(client.dest_addr)); 1.427 + 1.428 + if (this.test_cases.length == this.tests_completed) { 1.429 + print('server: all tests completed'); 1.430 + this.close(); 1.431 + do_test_finished(); 1.432 + } 1.433 + }, 1.434 + 1.435 + runClientSubprocess: function() 1.436 + { 1.437 + var argv = []; 1.438 + 1.439 + // marshaled: socks_ver|server_port|dest_host|dest_port|<remote|local> 1.440 + for each (var test in this.test_cases) { 1.441 + var arg = test.type + '|' + 1.442 + String(socks_listen_port) + '|' + 1.443 + test.host + '|' + test.port + '|'; 1.444 + if (test.remote_dns) 1.445 + arg += 'remote'; 1.446 + else 1.447 + arg += 'local'; 1.448 + print('server: using test case', arg); 1.449 + argv.push(arg); 1.450 + } 1.451 + 1.452 + this.client_subprocess = runScriptSubprocess( 1.453 + 'socks_client_subprocess.js', argv); 1.454 + }, 1.455 + 1.456 + onSocketAccepted: function(socket, trans) 1.457 + { 1.458 + print('server: got client connection'); 1.459 + var input = trans.openInputStream(0, 0, 0); 1.460 + var output = trans.openOutputStream(0, 0, 0); 1.461 + var client = new SocksClient(this, input, output); 1.462 + this.client_connections.push(client); 1.463 + }, 1.464 + 1.465 + onStopListening: function(socket) 1.466 + { 1.467 + }, 1.468 + 1.469 + close: function() 1.470 + { 1.471 + if (this.client_subprocess) { 1.472 + try { 1.473 + this.client_subprocess.kill(); 1.474 + } catch (x) { 1.475 + do_note_exception(x, 'Killing subprocess failed'); 1.476 + } 1.477 + this.client_subprocess = null; 1.478 + } 1.479 + for each (var client in this.client_connections) 1.480 + client.close(); 1.481 + this.client_connections = []; 1.482 + if (this.listener) { 1.483 + this.listener.close(); 1.484 + this.listener = null; 1.485 + } 1.486 + } 1.487 +}; 1.488 + 1.489 +function test_timeout() 1.490 +{ 1.491 + socks_test_server.close(); 1.492 + do_throw("SOCKS test took too long!"); 1.493 +} 1.494 + 1.495 +function run_test() 1.496 +{ 1.497 + socks_test_server = new SocksTestServer(); 1.498 + 1.499 + socks_test_server.addTestCase({ 1.500 + type: "socks4", 1.501 + host: '127.0.0.1', 1.502 + remote_dns: false, 1.503 + }); 1.504 + socks_test_server.addTestCase({ 1.505 + type: "socks4", 1.506 + host: '12345.xxx', 1.507 + remote_dns: true, 1.508 + }); 1.509 + socks_test_server.addTestCase({ 1.510 + type: "socks4", 1.511 + expectedType: "socks", 1.512 + host: '::1', 1.513 + remote_dns: false, 1.514 + }); 1.515 + socks_test_server.addTestCase({ 1.516 + type: "socks", 1.517 + host: '127.0.0.1', 1.518 + remote_dns: false, 1.519 + }); 1.520 + socks_test_server.addTestCase({ 1.521 + type: "socks", 1.522 + host: 'abcdefg.xxx', 1.523 + remote_dns: true, 1.524 + }); 1.525 + socks_test_server.addTestCase({ 1.526 + type: "socks", 1.527 + host: '::1', 1.528 + remote_dns: false, 1.529 + }); 1.530 + socks_test_server.runClientSubprocess(); 1.531 + 1.532 + do_timeout(120 * 1000, test_timeout); 1.533 + do_test_pending(); 1.534 +}