test_unix_domain.js (21547B)
1 // Exercise Unix domain sockets. 2 "use strict"; 3 4 var CC = Components.Constructor; 5 6 const UnixServerSocket = CC( 7 "@mozilla.org/network/server-socket;1", 8 "nsIServerSocket", 9 "initWithFilename" 10 ); 11 const UnixAbstractServerSocket = CC( 12 "@mozilla.org/network/server-socket;1", 13 "nsIServerSocket", 14 "initWithAbstractAddress" 15 ); 16 17 const ScriptableInputStream = CC( 18 "@mozilla.org/scriptableinputstream;1", 19 "nsIScriptableInputStream", 20 "init" 21 ); 22 23 const socketTransportService = Cc[ 24 "@mozilla.org/network/socket-transport-service;1" 25 ].getService(Ci.nsISocketTransportService); 26 27 const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(); 28 29 const allPermissions = parseInt("777", 8); 30 31 function run_test() { 32 // If we're on Windows, simply check for graceful failure. 33 if (mozinfo.os == "win") { 34 test_not_supported(); 35 return; 36 } 37 38 // The xpcshell temp directory on Android doesn't seem to let us create 39 // Unix domain sockets. (Perhaps it's a FAT filesystem?) 40 if (mozinfo.os != "android") { 41 add_test(test_echo); 42 add_test(test_name_too_long); 43 add_test(test_no_directory); 44 add_test(test_no_such_socket); 45 add_test(test_address_in_use); 46 add_test(test_file_in_way); 47 add_test(test_create_permission); 48 add_test(test_connect_permission); 49 add_test(test_long_socket_name); 50 add_test(test_keep_when_offline); 51 } 52 53 if (mozinfo.os == "android" || mozinfo.os == "linux") { 54 add_test(test_abstract_address_socket); 55 } 56 57 run_next_test(); 58 } 59 60 // Check that creating a Unix domain socket fails gracefully on Windows. 61 function test_not_supported() { 62 let socketName = do_get_tempdir(); 63 socketName.append("socket"); 64 info("creating socket: " + socketName.path); 65 66 do_check_throws_nsIException( 67 () => new UnixServerSocket(socketName, allPermissions, -1), 68 "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED" 69 ); 70 71 do_check_throws_nsIException( 72 () => socketTransportService.createUnixDomainTransport(socketName), 73 "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED" 74 ); 75 } 76 77 // Actually exchange data with Unix domain sockets. 78 function test_echo() { 79 let log = ""; 80 81 let socketName = do_get_tempdir(); 82 socketName.append("socket"); 83 84 // Create a server socket, listening for connections. 85 info("creating socket: " + socketName.path); 86 let server = new UnixServerSocket(socketName, allPermissions, -1); 87 server.asyncListen({ 88 onSocketAccepted(aServ, aTransport) { 89 info("called test_echo's onSocketAccepted"); 90 log += "a"; 91 92 Assert.equal(aServ, server); 93 94 let connection = aTransport; 95 96 // Check the server socket's self address. 97 let connectionSelfAddr = connection.getScriptableSelfAddr(); 98 Assert.equal(connectionSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); 99 Assert.equal(connectionSelfAddr.address, socketName.path); 100 101 // The client socket is anonymous, so the server transport should 102 // have an empty peer address. 103 Assert.equal(connection.host, ""); 104 Assert.equal(connection.port, 0); 105 let connectionPeerAddr = connection.getScriptablePeerAddr(); 106 Assert.equal(connectionPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); 107 Assert.equal(connectionPeerAddr.address, ""); 108 109 let serverAsyncInput = connection 110 .openInputStream(0, 0, 0) 111 .QueryInterface(Ci.nsIAsyncInputStream); 112 let serverOutput = connection.openOutputStream(0, 0, 0); 113 114 serverAsyncInput.asyncWait( 115 function (aStream) { 116 info("called test_echo's server's onInputStreamReady"); 117 let serverScriptableInput = new ScriptableInputStream(aStream); 118 119 // Receive data from the client, and send back a response. 120 Assert.equal( 121 serverScriptableInput.readBytes(17), 122 "Mervyn Murgatroyd" 123 ); 124 info("server has read message from client"); 125 serverOutput.write("Ruthven Murgatroyd", 18); 126 info("server has written to client"); 127 }, 128 0, 129 0, 130 threadManager.currentThread 131 ); 132 }, 133 134 onStopListening(aServ) { 135 info("called test_echo's onStopListening"); 136 log += "s"; 137 138 Assert.equal(aServ, server); 139 Assert.equal(log, "acs"); 140 141 run_next_test(); 142 }, 143 }); 144 145 // Create a client socket, and connect to the server. 146 let client = socketTransportService.createUnixDomainTransport(socketName); 147 Assert.equal(client.host, socketName.path); 148 Assert.equal(client.port, 0); 149 150 let clientAsyncInput = client 151 .openInputStream(0, 0, 0) 152 .QueryInterface(Ci.nsIAsyncInputStream); 153 let clientInput = new ScriptableInputStream(clientAsyncInput); 154 let clientOutput = client.openOutputStream(0, 0, 0); 155 156 clientOutput.write("Mervyn Murgatroyd", 17); 157 info("client has written to server"); 158 159 clientAsyncInput.asyncWait( 160 function (aStream) { 161 info("called test_echo's client's onInputStreamReady"); 162 log += "c"; 163 164 Assert.equal(aStream, clientAsyncInput); 165 166 // Now that the connection has been established, we can check the 167 // transport's self and peer addresses. 168 let clientSelfAddr = client.getScriptableSelfAddr(); 169 Assert.equal(clientSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); 170 Assert.equal(clientSelfAddr.address, ""); 171 172 Assert.equal(client.host, socketName.path); // re-check, but hey 173 let clientPeerAddr = client.getScriptablePeerAddr(); 174 Assert.equal(clientPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); 175 Assert.equal(clientPeerAddr.address, socketName.path); 176 177 Assert.equal(clientInput.readBytes(18), "Ruthven Murgatroyd"); 178 info("client has read message from server"); 179 180 server.close(); 181 }, 182 0, 183 0, 184 threadManager.currentThread 185 ); 186 } 187 188 // Create client and server sockets using a path that's too long. 189 function test_name_too_long() { 190 let socketName = do_get_tempdir(); 191 // The length limits on all the systems NSPR supports are a bit past 100. 192 socketName.append(new Array(1000).join("x")); 193 194 // The length must be checked before we ever make any system calls --- we 195 // have to create the sockaddr first --- so it's unambiguous which error 196 // we should get here. 197 198 do_check_throws_nsIException( 199 () => new UnixServerSocket(socketName, 0, -1), 200 "NS_ERROR_FILE_NAME_TOO_LONG" 201 ); 202 203 // Unlike most other client socket errors, this one gets reported 204 // immediately, as we can't even initialize the sockaddr with the given 205 // name. 206 do_check_throws_nsIException( 207 () => socketTransportService.createUnixDomainTransport(socketName), 208 "NS_ERROR_FILE_NAME_TOO_LONG" 209 ); 210 211 run_next_test(); 212 } 213 214 // Try creating a socket in a directory that doesn't exist. 215 function test_no_directory() { 216 let socketName = do_get_tempdir(); 217 socketName.append("missing"); 218 socketName.append("socket"); 219 220 do_check_throws_nsIException( 221 () => new UnixServerSocket(socketName, 0, -1), 222 "NS_ERROR_FILE_NOT_FOUND" 223 ); 224 225 run_next_test(); 226 } 227 228 // Try connecting to a server socket that isn't there. 229 function test_no_such_socket() { 230 let socketName = do_get_tempdir(); 231 socketName.append("nonexistent-socket"); 232 233 let client = socketTransportService.createUnixDomainTransport(socketName); 234 let clientAsyncInput = client 235 .openInputStream(0, 0, 0) 236 .QueryInterface(Ci.nsIAsyncInputStream); 237 clientAsyncInput.asyncWait( 238 function (aStream) { 239 info("called test_no_such_socket's onInputStreamReady"); 240 241 Assert.equal(aStream, clientAsyncInput); 242 243 // nsISocketTransport puts off actually creating sockets as long as 244 // possible, so the error in connecting doesn't actually show up until 245 // this point. 246 do_check_throws_nsIException( 247 () => clientAsyncInput.available(), 248 "NS_ERROR_FILE_NOT_FOUND" 249 ); 250 251 clientAsyncInput.close(); 252 client.close(Cr.NS_OK); 253 254 run_next_test(); 255 }, 256 0, 257 0, 258 threadManager.currentThread 259 ); 260 } 261 262 // Creating a socket with a name that another socket is already using is an 263 // error. 264 function test_address_in_use() { 265 let socketName = do_get_tempdir(); 266 socketName.append("socket-in-use"); 267 268 // Create one server socket. 269 new UnixServerSocket(socketName, allPermissions, -1); 270 271 // Now try to create another with the same name. 272 do_check_throws_nsIException( 273 () => new UnixServerSocket(socketName, allPermissions, -1), 274 "NS_ERROR_SOCKET_ADDRESS_IN_USE" 275 ); 276 277 run_next_test(); 278 } 279 280 // Creating a socket with a name that is already a file is an error. 281 function test_file_in_way() { 282 let socketName = do_get_tempdir(); 283 socketName.append("file_in_way"); 284 285 // Create a file with the given name. 286 socketName.create(Ci.nsIFile.NORMAL_FILE_TYPE, allPermissions); 287 288 // Try to create a socket with the same name. 289 do_check_throws_nsIException( 290 () => new UnixServerSocket(socketName, allPermissions, -1), 291 "NS_ERROR_SOCKET_ADDRESS_IN_USE" 292 ); 293 294 // Try to create a socket under a name that uses that as a parent directory. 295 socketName.append("socket"); 296 do_check_throws_nsIException( 297 () => new UnixServerSocket(socketName, 0, -1), 298 "NS_ERROR_FILE_NOT_DIRECTORY" 299 ); 300 301 run_next_test(); 302 } 303 304 // It is not permitted to create a socket in a directory which we are not 305 // permitted to execute, or create files in. 306 function test_create_permission() { 307 let dirName = do_get_tempdir(); 308 dirName.append("unfriendly"); 309 310 let socketName = dirName.clone(); 311 socketName.append("socket"); 312 313 // The test harness has difficulty cleaning things up if we don't make 314 // everything writable before we're done. 315 try { 316 // Create a directory which we are not permitted to search. 317 dirName.create(Ci.nsIFile.DIRECTORY_TYPE, 0); 318 319 // Try to create a socket in that directory. Because Linux returns EACCES 320 // when a 'connect' fails because of a local firewall rule, 321 // nsIServerSocket returns NS_ERROR_CONNECTION_REFUSED in this case. 322 do_check_throws_nsIException( 323 () => new UnixServerSocket(socketName, allPermissions, -1), 324 "NS_ERROR_CONNECTION_REFUSED" 325 ); 326 327 // Grant read and execute permission, but not write permission on the directory. 328 dirName.permissions = parseInt("0555", 8); 329 330 // This should also fail; we need write permission. 331 do_check_throws_nsIException( 332 () => new UnixServerSocket(socketName, allPermissions, -1), 333 "NS_ERROR_CONNECTION_REFUSED" 334 ); 335 } finally { 336 // Make the directory writable, so the test harness can clean it up. 337 dirName.permissions = allPermissions; 338 } 339 340 // This should succeed, since we now have all the permissions on the 341 // directory we could want. 342 do_check_instanceof( 343 new UnixServerSocket(socketName, allPermissions, -1), 344 Ci.nsIServerSocket 345 ); 346 347 run_next_test(); 348 } 349 350 // To connect to a Unix domain socket, we need search permission on the 351 // directories containing it, and some kind of permission or other on the 352 // socket itself. 353 function test_connect_permission() { 354 // This test involves a lot of callbacks, but they're written out so that 355 // the actual control flow proceeds from top to bottom. 356 let log = ""; 357 358 // Create a directory which we are permitted to search - at first. 359 let dirName = do_get_tempdir(); 360 dirName.append("inhospitable"); 361 dirName.create(Ci.nsIFile.DIRECTORY_TYPE, allPermissions); 362 363 let socketName = dirName.clone(); 364 socketName.append("socket"); 365 366 // Create a server socket in that directory, listening for connections, 367 // and accessible. 368 let server = new UnixServerSocket(socketName, allPermissions, -1); 369 server.asyncListen({ 370 onSocketAccepted: socketAccepted, 371 onStopListening: stopListening, 372 }); 373 374 // Make the directory unsearchable. 375 dirName.permissions = 0; 376 377 let client3; 378 379 let client1 = socketTransportService.createUnixDomainTransport(socketName); 380 let client1AsyncInput = client1 381 .openInputStream(0, 0, 0) 382 .QueryInterface(Ci.nsIAsyncInputStream); 383 client1AsyncInput.asyncWait( 384 function () { 385 info("called test_connect_permission's client1's onInputStreamReady"); 386 log += "1"; 387 388 // nsISocketTransport puts off actually creating sockets as long as 389 // possible, so the error doesn't actually show up until this point. 390 do_check_throws_nsIException( 391 () => client1AsyncInput.available(), 392 "NS_ERROR_CONNECTION_REFUSED" 393 ); 394 395 client1AsyncInput.close(); 396 client1.close(Cr.NS_OK); 397 398 // Make the directory searchable, but make the socket inaccessible. 399 dirName.permissions = allPermissions; 400 socketName.permissions = 0; 401 402 let client2 = 403 socketTransportService.createUnixDomainTransport(socketName); 404 let client2AsyncInput = client2 405 .openInputStream(0, 0, 0) 406 .QueryInterface(Ci.nsIAsyncInputStream); 407 client2AsyncInput.asyncWait( 408 function () { 409 info("called test_connect_permission's client2's onInputStreamReady"); 410 log += "2"; 411 412 do_check_throws_nsIException( 413 () => client2AsyncInput.available(), 414 "NS_ERROR_CONNECTION_REFUSED" 415 ); 416 417 client2AsyncInput.close(); 418 client2.close(Cr.NS_OK); 419 420 // Now make everything accessible, and try one last time. 421 socketName.permissions = allPermissions; 422 423 client3 = 424 socketTransportService.createUnixDomainTransport(socketName); 425 426 let client3Output = client3.openOutputStream(0, 0, 0); 427 client3Output.write("Hanratty", 8); 428 429 let client3AsyncInput = client3 430 .openInputStream(0, 0, 0) 431 .QueryInterface(Ci.nsIAsyncInputStream); 432 client3AsyncInput.asyncWait( 433 client3InputStreamReady, 434 0, 435 0, 436 threadManager.currentThread 437 ); 438 }, 439 0, 440 0, 441 threadManager.currentThread 442 ); 443 }, 444 0, 445 0, 446 threadManager.currentThread 447 ); 448 449 function socketAccepted(aServ, aTransport) { 450 info("called test_connect_permission's onSocketAccepted"); 451 log += "a"; 452 453 let serverInput = aTransport 454 .openInputStream(0, 0, 0) 455 .QueryInterface(Ci.nsIAsyncInputStream); 456 let serverOutput = aTransport.openOutputStream(0, 0, 0); 457 458 serverInput.asyncWait( 459 function () { 460 info( 461 "called test_connect_permission's socketAccepted's onInputStreamReady" 462 ); 463 log += "i"; 464 465 // Receive data from the client, and send back a response. 466 let serverScriptableInput = new ScriptableInputStream(serverInput); 467 Assert.equal(serverScriptableInput.readBytes(8), "Hanratty"); 468 serverOutput.write("Ferlingatti", 11); 469 }, 470 0, 471 0, 472 threadManager.currentThread 473 ); 474 } 475 476 function client3InputStreamReady(aStream) { 477 info("called client3's onInputStreamReady"); 478 log += "3"; 479 480 let client3Input = new ScriptableInputStream(aStream); 481 482 Assert.equal(client3Input.readBytes(11), "Ferlingatti"); 483 484 client3.close(Cr.NS_OK); 485 server.close(); 486 } 487 488 function stopListening() { 489 info("called test_connect_permission's server's stopListening"); 490 log += "s"; 491 492 Assert.equal(log, "12ai3s"); 493 494 run_next_test(); 495 } 496 } 497 498 // Creating a socket with a long filename doesn't crash. 499 function test_long_socket_name() { 500 let socketName = do_get_tempdir(); 501 socketName.append(new Array(10000).join("long")); 502 503 // Try to create a server socket with the long name. 504 do_check_throws_nsIException( 505 () => new UnixServerSocket(socketName, allPermissions, -1), 506 "NS_ERROR_FILE_NAME_TOO_LONG" 507 ); 508 509 // Try to connect to a socket with the long name. 510 do_check_throws_nsIException( 511 () => socketTransportService.createUnixDomainTransport(socketName), 512 "NS_ERROR_FILE_NAME_TOO_LONG" 513 ); 514 515 run_next_test(); 516 } 517 518 // Going offline should not shut down Unix domain sockets. 519 function test_keep_when_offline() { 520 let log = ""; 521 522 let socketName = do_get_tempdir(); 523 socketName.append("keep-when-offline"); 524 525 // Create a listening socket. 526 let listener = new UnixServerSocket(socketName, allPermissions, -1); 527 listener.asyncListen({ onSocketAccepted: onAccepted, onStopListening }); 528 529 // Connect a client socket to the listening socket. 530 let client = socketTransportService.createUnixDomainTransport(socketName); 531 let clientOutput = client.openOutputStream(0, 0, 0); 532 let clientInput = client.openInputStream(0, 0, 0); 533 clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread); 534 let clientScriptableInput = new ScriptableInputStream(clientInput); 535 536 let server, serverInput, serverScriptableInput, serverOutput; 537 538 // How many times has the server invited the client to go first? 539 let count = 0; 540 541 // The server accepted connection callback. 542 function onAccepted(aListener, aServer) { 543 info("test_keep_when_offline: onAccepted called"); 544 log += "a"; 545 Assert.equal(aListener, listener); 546 server = aServer; 547 548 // Prepare to receive messages from the client. 549 serverInput = server.openInputStream(0, 0, 0); 550 serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread); 551 serverScriptableInput = new ScriptableInputStream(serverInput); 552 553 // Start a conversation with the client. 554 serverOutput = server.openOutputStream(0, 0, 0); 555 serverOutput.write("After you, Alphonse!", 20); 556 count++; 557 } 558 559 // The client has seen its end of the socket close. 560 function clientReady(aStream) { 561 log += "c"; 562 info("test_keep_when_offline: clientReady called: " + log); 563 Assert.equal(aStream, clientInput); 564 565 // If the connection has been closed, end the conversation and stop listening. 566 let available; 567 try { 568 available = clientInput.available(); 569 } catch (ex) { 570 do_check_instanceof(ex, Ci.nsIException); 571 Assert.equal(ex.result, Cr.NS_BASE_STREAM_CLOSED); 572 573 info("client received end-of-stream; closing client output stream"); 574 log += ")"; 575 576 client.close(Cr.NS_OK); 577 578 // Now both output streams have been closed, and both input streams 579 // have received the close notification. Stop listening for 580 // connections. 581 listener.close(); 582 } 583 584 if (available) { 585 // Check the message from the server. 586 Assert.equal(clientScriptableInput.readBytes(20), "After you, Alphonse!"); 587 588 // Write our response to the server. 589 clientOutput.write("No, after you, Gaston!", 22); 590 591 // Ask to be called again, when more input arrives. 592 clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread); 593 } 594 } 595 596 function serverReady(aStream) { 597 log += "s"; 598 info("test_keep_when_offline: serverReady called: " + log); 599 Assert.equal(aStream, serverInput); 600 601 // Check the message from the client. 602 Assert.equal(serverScriptableInput.readBytes(22), "No, after you, Gaston!"); 603 604 // This should not shut things down: Unix domain sockets should 605 // remain open in offline mode. 606 if (count == 5) { 607 Services.io.offline = true; 608 log += "o"; 609 } 610 611 if (count < 10) { 612 // Insist. 613 serverOutput.write("After you, Alphonse!", 20); 614 count++; 615 616 // As long as the input stream is open, always ask to be called again 617 // when more input arrives. 618 serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread); 619 } else if (count == 10) { 620 // After sending ten times and receiving ten replies, we're not 621 // going to send any more. Close the server's output stream; the 622 // client's input stream should see this. 623 info("closing server transport"); 624 server.close(Cr.NS_OK); 625 log += "("; 626 } 627 } 628 629 // We have stopped listening. 630 function onStopListening(aServ, aStatus) { 631 info("test_keep_when_offline: onStopListening called"); 632 log += "L"; 633 Assert.equal(log, "acscscscscsocscscscscs(c)L"); 634 635 Assert.equal(aServ, listener); 636 Assert.equal(aStatus, Cr.NS_BINDING_ABORTED); 637 638 run_next_test(); 639 } 640 } 641 642 function test_abstract_address_socket() { 643 const socketname = "abstractsocket"; 644 let server = new UnixAbstractServerSocket(socketname, -1); 645 server.asyncListen({ 646 onSocketAccepted: (aServ, aTransport) => { 647 let serverInput = aTransport 648 .openInputStream(0, 0, 0) 649 .QueryInterface(Ci.nsIAsyncInputStream); 650 let serverOutput = aTransport.openOutputStream(0, 0, 0); 651 652 serverInput.asyncWait( 653 () => { 654 info( 655 "called test_abstract_address_socket's onSocketAccepted's onInputStreamReady" 656 ); 657 658 // Receive data from the client, and send back a response. 659 let serverScriptableInput = new ScriptableInputStream(serverInput); 660 Assert.equal(serverScriptableInput.readBytes(9), "ping ping"); 661 serverOutput.write("pong", 4); 662 }, 663 0, 664 0, 665 threadManager.currentThread 666 ); 667 }, 668 onStopListening: () => {}, 669 }); 670 671 let client = 672 socketTransportService.createUnixDomainAbstractAddressTransport(socketname); 673 Assert.equal(client.host, socketname); 674 Assert.equal(client.port, 0); 675 let clientInput = client 676 .openInputStream(0, 0, 0) 677 .QueryInterface(Ci.nsIAsyncInputStream); 678 let clientOutput = client.openOutputStream(0, 0, 0); 679 680 clientOutput.write("ping ping", 9); 681 682 clientInput.asyncWait( 683 () => { 684 let clientScriptInput = new ScriptableInputStream(clientInput); 685 let available = clientScriptInput.available(); 686 if (available) { 687 Assert.equal(clientScriptInput.readBytes(4), "pong"); 688 689 client.close(Cr.NS_OK); 690 server.close(Cr.NS_OK); 691 692 run_next_test(); 693 } 694 }, 695 0, 696 0, 697 threadManager.currentThread 698 ); 699 }