diff --git a/src/Server.php b/src/Server.php index 3309635..aa159ab 100644 --- a/src/Server.php +++ b/src/Server.php @@ -119,8 +119,10 @@ public function onConnection(ConnectionInterface $connection) { $that = $this; $handling = $this->handleSocks($connection)->then(null, function () use ($connection, $that) { - // SOCKS failed => close connection - $that->endConnection($connection); + // SOCKS failed => close connection (unless already closed) + if ($connection->isWritable()) { + $that->endConnection($connection); + } }); $connection->on('close', function () use ($handling) { diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 3748181..6293f87 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -225,9 +225,10 @@ public function testConnectWillReturnMappedSocks5ErrorCodeFromConnector($error, public function testHandleSocksConnectionWillEndOnInvalidData() { - $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock(); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'isWritable'))->getMock(); $connection->expects($this->once())->method('pause'); $connection->expects($this->once())->method('end'); + $connection->expects($this->once())->method('isWritable')->willReturn(true); $this->server->onConnection($connection); @@ -290,7 +291,8 @@ public function testHandleSocks4aConnectionWithSecureTlsSourceAddressWillEstabli public function testHandleSocks4aConnectionWithInvalidHostnameWillNotEstablishOutgoingConnection() { - $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock(); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'isWritable'))->getMock(); + $connection->expects($this->once())->method('isWritable')->willReturn(true); $this->connector->expects($this->never())->method('connect'); @@ -368,7 +370,8 @@ public function testHandleSocks5ConnectionWithHostnameWillEstablishOutgoingConne public function testHandleSocks5ConnectionWithConnectorRefusedWillReturnReturnRefusedError() { - $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock(); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'isWritable'))->getMock(); + $connection->expects($this->once())->method('isWritable')->willReturn(true); $promise = \React\Promise\reject(new \RuntimeException('Connection refused')); @@ -383,7 +386,8 @@ public function testHandleSocks5ConnectionWithConnectorRefusedWillReturnReturnRe public function testHandleSocks5UdpCommandWillNotEstablishOutgoingConnectionAndReturnCommandError() { - $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock(); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'isWritable'))->getMock(); + $connection->expects($this->once())->method('isWritable')->willReturn(true); $this->connector->expects($this->never())->method('connect'); @@ -396,7 +400,8 @@ public function testHandleSocks5UdpCommandWillNotEstablishOutgoingConnectionAndR public function testHandleSocks5ConnectionWithInvalidHostnameWillNotEstablishOutgoingConnectionAndReturnGeneralError() { - $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock(); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'isWritable'))->getMock(); + $connection->expects($this->once())->method('isWritable')->willReturn(true); $this->connector->expects($this->never())->method('connect'); @@ -420,4 +425,23 @@ public function testHandleSocksConnectionWillCancelOutputConnectionIfIncomingClo $connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00")); $connection->emit('close'); } + + public function testHandleSocksConnectionWillNotCallEndConnectionIfAlreadyClosed() + { + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'isWritable'))->getMock(); + $connection->expects($this->never())->method('pause'); + $connection->expects($this->once())->method('end'); + $connection->expects($this->once())->method('isWritable')->willReturn(false); + + $promise = new Promise(function () { }, function () { + throw new \RuntimeException(); + }); + + $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise); + + $this->server->onConnection($connection); + + $connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00")); + $connection->emit('close'); + } }