| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946 |
- <?php
- namespace React\Tests\Dns\Query;
- use React\Dns\Model\Message;
- use React\Dns\Protocol\BinaryDumper;
- use React\Dns\Protocol\Parser;
- use React\Dns\Query\Query;
- use React\Dns\Query\TcpTransportExecutor;
- use React\EventLoop\Loop;
- use React\Tests\Dns\TestCase;
- class TcpTransportExecutorTest extends TestCase
- {
- /**
- * @dataProvider provideDefaultPortProvider
- * @param string $input
- * @param string $expected
- */
- public function testCtorShouldAcceptNameserverAddresses($input, $expected)
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $executor = new TcpTransportExecutor($input, $loop);
- $ref = new \ReflectionProperty($executor, 'nameserver');
- $ref->setAccessible(true);
- $value = $ref->getValue($executor);
- $this->assertEquals($expected, $value);
- }
- public static function provideDefaultPortProvider()
- {
- return array(
- array(
- '8.8.8.8',
- 'tcp://8.8.8.8:53'
- ),
- array(
- '1.2.3.4:5',
- 'tcp://1.2.3.4:5'
- ),
- array(
- 'tcp://1.2.3.4',
- 'tcp://1.2.3.4:53'
- ),
- array(
- 'tcp://1.2.3.4:53',
- 'tcp://1.2.3.4:53'
- ),
- array(
- '::1',
- 'tcp://[::1]:53'
- ),
- array(
- '[::1]:53',
- 'tcp://[::1]:53'
- )
- );
- }
- public function testCtorWithoutLoopShouldAssignDefaultLoop()
- {
- $executor = new TcpTransportExecutor('127.0.0.1');
- $ref = new \ReflectionProperty($executor, 'loop');
- $ref->setAccessible(true);
- $loop = $ref->getValue($executor);
- $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop);
- }
- public function testCtorShouldThrowWhenNameserverAddressIsInvalid()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $this->setExpectedException('InvalidArgumentException');
- new TcpTransportExecutor('///', $loop);
- }
- public function testCtorShouldThrowWhenNameserverAddressContainsHostname()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $this->setExpectedException('InvalidArgumentException');
- new TcpTransportExecutor('localhost', $loop);
- }
- public function testCtorShouldThrowWhenNameserverSchemeIsInvalid()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $this->setExpectedException('InvalidArgumentException');
- new TcpTransportExecutor('udp://1.2.3.4', $loop);
- }
- public function testQueryRejectsIfMessageExceedsMaximumMessageSize()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->never())->method('addWriteStream');
- $executor = new TcpTransportExecutor('8.8.8.8:53', $loop);
- $query = new Query('google.' . str_repeat('.com', 60000), Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- $exception = null;
- $promise->then(null, function ($reason) use (&$exception) {
- $exception = $reason;
- });
- /** @var \RuntimeException $exception */
- $this->assertInstanceOf('RuntimeException', $exception);
- $this->assertEquals('DNS query for '. $query->name . ' (A) failed: Query too large for TCP transport', $exception->getMessage());
- }
- public function testQueryRejectsIfServerConnectionFails()
- {
- if (defined('HHVM_VERSION')) {
- $this->markTestSkipped('HHVM reports different error message for invalid addresses');
- }
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->never())->method('addWriteStream');
- $executor = new TcpTransportExecutor('::1', $loop);
- $ref = new \ReflectionProperty($executor, 'nameserver');
- $ref->setAccessible(true);
- $ref->setValue($executor, '///');
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- $exception = null;
- $promise->then(null, function ($reason) use (&$exception) {
- $exception = $reason;
- });
- /** @var \RuntimeException $exception */
- $this->assertInstanceOf('RuntimeException', $exception);
- $this->assertEquals('DNS query for google.com (A) failed: Unable to connect to DNS server /// (Failed to parse address "///")', $exception->getMessage());
- }
- public function testQueryRejectsOnCancellationWithoutClosingSocketButStartsIdleTimer()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->never())->method('removeWriteStream');
- $loop->expects($this->never())->method('addReadStream');
- $loop->expects($this->never())->method('removeReadStream');
- $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
- $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);
- $loop->expects($this->never())->method('cancelTimer');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- $promise->cancel();
- $exception = null;
- $promise->then(null, function ($reason) use (&$exception) {
- $exception = $reason;
- });
- /** @var \React\Dns\Query\CancellationException $exception */
- $this->assertInstanceOf('React\Dns\Query\CancellationException', $exception);
- $this->assertEquals('DNS query for google.com (A) has been cancelled', $exception->getMessage());
- }
- public function testTriggerIdleTimerAfterQueryRejectedOnCancellationWillCloseSocket()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->once())->method('removeWriteStream');
- $loop->expects($this->never())->method('addReadStream');
- $loop->expects($this->never())->method('removeReadStream');
- $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
- $timerCallback = null;
- $loop->expects($this->once())->method('addTimer')->with(0.001, $this->callback(function ($cb) use (&$timerCallback) {
- $timerCallback = $cb;
- return true;
- }))->willReturn($timer);
- $loop->expects($this->once())->method('cancelTimer')->with($timer);
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- $promise->cancel();
- $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
- $promise->then(null, $this->expectCallableOnce());
- // trigger idle timer
- $this->assertNotNull($timerCallback);
- $timerCallback();
- }
- public function testQueryRejectsOnCancellationWithoutClosingSocketAndWithoutStartingIdleTimerWhenOtherQueryIsStillPending()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->never())->method('removeWriteStream');
- $loop->expects($this->never())->method('addReadStream');
- $loop->expects($this->never())->method('removeReadStream');
- $loop->expects($this->never())->method('addTimer');
- $loop->expects($this->never())->method('cancelTimer');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise1 = $executor->query($query);
- $promise2 = $executor->query($query);
- $promise2->cancel();
- $promise1->then($this->expectCallableNever(), $this->expectCallableNever());
- $promise2->then(null, $this->expectCallableOnce());
- }
- public function testQueryAgainAfterPreviousWasCancelledReusesExistingSocket()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->never())->method('removeWriteStream');
- $loop->expects($this->never())->method('addReadStream');
- $loop->expects($this->never())->method('removeReadStream');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- $promise->cancel();
- $executor->query($query);
- }
- public function testQueryRejectsWhenServerIsNotListening()
- {
- $executor = new TcpTransportExecutor('127.0.0.1:1');
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $exception = null;
- $executor->query($query)->then(
- null,
- function ($e) use (&$exception) {
- $exception = $e;
- }
- );
- \React\Async\await(\React\Promise\Timer\sleep(0.01));
- if ($exception === null) {
- \React\Async\await(\React\Promise\Timer\sleep(0.2));
- }
- /** @var \RuntimeException $exception */
- $this->assertInstanceOf('RuntimeException', $exception);
- $this->assertEquals('DNS query for google.com (A) failed: Unable to connect to DNS server tcp://127.0.0.1:1 (Connection refused)', $exception->getMessage());
- $this->assertEquals(defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $exception->getCode());
- }
- public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneChunk()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->never())->method('removeWriteStream');
- $loop->expects($this->never())->method('removeReadStream');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google' . str_repeat('.com', 100), Message::TYPE_A, Message::CLASS_IN);
- // send a bunch of queries and keep reference to last promise
- for ($i = 0; $i < 8000; ++$i) {
- $promise = $executor->query($query);
- }
- $client = stream_socket_accept($server);
- assert(is_resource($client));
- $executor->handleWritable();
- $promise->then(null, 'printf');
- $promise->then($this->expectCallableNever(), $this->expectCallableNever());
- $ref = new \ReflectionProperty($executor, 'writePending');
- $ref->setAccessible(true);
- $writePending = $ref->getValue($executor);
- $this->assertTrue($writePending);
- }
- public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneChunkWhenServerClosesSocket()
- {
- if (PHP_OS === 'Darwin') {
- // Skip on macOS because it exhibits what looks like a kernal race condition when sending excessive data to a socket that is about to shut down (EPROTOTYPE)
- // Due to this race condition, this is somewhat flaky. Happens around 75% of the time, use `--repeat=100` to reproduce.
- // fwrite(): Send of 4260000 bytes failed with errno=41 Protocol wrong type for socket
- // @link http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/
- $this->markTestSkipped('Skipped on macOS due to possible race condition');
- }
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->never())->method('removeWriteStream');
- $loop->expects($this->never())->method('removeReadStream');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google' . str_repeat('.com', 100), Message::TYPE_A, Message::CLASS_IN);
- // send a bunch of queries and keep reference to last promise
- for ($i = 0; $i < 2000; ++$i) {
- $promise = $executor->query($query);
- }
- $client = stream_socket_accept($server);
- fclose($client);
- $executor->handleWritable();
- $promise->then($this->expectCallableNever(), $this->expectCallableNever());
- $ref = new \ReflectionProperty($executor, 'writePending');
- $ref->setAccessible(true);
- $writePending = $ref->getValue($executor);
- $this->assertTrue($writePending);
- }
- public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocketWithoutCallingCustomErrorHandler()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->once())->method('removeWriteStream');
- $loop->expects($this->once())->method('removeReadStream');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google' . str_repeat('.com', 100), Message::TYPE_A, Message::CLASS_IN);
- // send a bunch of queries and keep reference to last promise
- $exception = null;
- for ($i = 0; $i < 2000; ++$i) {
- $promise = $executor->query($query);
- $promise->then(null, function (\Exception $reason) use (&$exception) {
- $exception = $reason;
- });
- }
- $client = stream_socket_accept($server);
- fclose($client);
- $error = null;
- set_error_handler(function ($_, $errstr) use (&$error) {
- $error = $errstr;
- });
- $executor->handleWritable();
- $ref = new \ReflectionProperty($executor, 'writePending');
- $ref->setAccessible(true);
- $writePending = $ref->getValue($executor);
- // We expect an EPIPE (Broken pipe) on second write.
- // However, macOS may report EPROTOTYPE (Protocol wrong type for socket) on first write due to kernel race condition.
- // fwrite(): Send of 4260000 bytes failed with errno=41 Protocol wrong type for socket
- // @link http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/
- if ($writePending) {
- $executor->handleWritable();
- }
- restore_error_handler();
- $this->assertNull($error);
- // expect EPIPE (Broken pipe), except for macOS kernel race condition or legacy HHVM
- $this->setExpectedException(
- 'RuntimeException',
- 'Unable to send query to DNS server tcp://' . $address . ' (',
- defined('SOCKET_EPIPE') && !defined('HHVM_VERSION') ? (PHP_OS !== 'Darwin' || $writePending ? SOCKET_EPIPE : SOCKET_EPROTOTYPE) : null
- );
- throw $exception;
- }
- public function testQueryRejectsWhenServerClosesConnection()
- {
- $server = stream_socket_server('tcp://127.0.0.1:0');
- Loop::addReadStream($server, function ($server) {
- $client = stream_socket_accept($server);
- fclose($client);
- Loop::removeReadStream($server);
- fclose($server);
- });
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $exception = null;
- $executor->query($query)->then(
- null,
- function ($e) use (&$exception) {
- $exception = $e;
- }
- );
- \React\Async\await(\React\Promise\Timer\sleep(0.01));
- if ($exception === null) {
- \React\Async\await(\React\Promise\Timer\sleep(0.2));
- }
- /** @var \RuntimeException $exception */
- $this->assertInstanceOf('RuntimeException', $exception);
- $this->assertEquals('DNS query for google.com (A) failed: Connection to DNS server tcp://' . $address . ' lost', $exception->getMessage());
- }
- public function testQueryKeepsPendingIfServerSendsIncompleteMessageLength()
- {
- $client = null;
- $server = stream_socket_server('tcp://127.0.0.1:0');
- Loop::addReadStream($server, function ($server) use (&$client) {
- $client = stream_socket_accept($server);
- Loop::addReadStream($client, function ($client) {
- Loop::removeReadStream($client);
- fwrite($client, "\x00");
- });
- Loop::removeReadStream($server);
- fclose($server);
- });
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $wait = true;
- $executor->query($query)->then(
- null,
- function ($e) use (&$wait) {
- $wait = false;
- }
- );
- \React\Async\await(\React\Promise\Timer\sleep(0.2));
- $this->assertTrue($wait);
- $this->assertNotNull($client);
- fclose($client);
- Loop::removeReadStream($client);
- }
- public function testQueryKeepsPendingIfServerSendsIncompleteMessageBody()
- {
- $client = null;
- $server = stream_socket_server('tcp://127.0.0.1:0');
- Loop::addReadStream($server, function ($server) use (&$client) {
- $client = stream_socket_accept($server);
- Loop::addReadStream($client, function ($client) {
- Loop::removeReadStream($client);
- fwrite($client, "\x00\xff" . "some incomplete message data");
- });
- Loop::removeReadStream($server);
- fclose($server);
- });
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $wait = true;
- $executor->query($query)->then(
- null,
- function ($e) use (&$wait) {
- $wait = false;
- }
- );
- \React\Async\await(\React\Promise\Timer\sleep(0.2));
- $this->assertTrue($wait);
- $this->assertNotNull($client);
- fclose($client);
- Loop::removeReadStream($client);
- }
- public function testQueryRejectsWhenServerSendsInvalidMessage()
- {
- $server = stream_socket_server('tcp://127.0.0.1:0');
- Loop::addReadStream($server, function ($server) {
- $client = stream_socket_accept($server);
- Loop::addReadStream($client, function ($client) {
- Loop::removeReadStream($client);
- fwrite($client, "\x00\x0f" . 'invalid message');
- });
- Loop::removeReadStream($server);
- fclose($server);
- });
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $exception = null;
- $executor->query($query)->then(
- null,
- function ($e) use (&$exception) {
- $exception = $e;
- }
- );
- \React\Async\await(\React\Promise\Timer\sleep(0.01));
- if ($exception === null) {
- \React\Async\await(\React\Promise\Timer\sleep(0.2));
- }
- /** @var \RuntimeException $exception */
- $this->assertInstanceOf('RuntimeException', $exception);
- $this->assertEquals('DNS query for google.com (A) failed: Invalid message received from DNS server tcp://' . $address, $exception->getMessage());
- }
- public function testQueryRejectsWhenServerSendsInvalidId()
- {
- $parser = new Parser();
- $dumper = new BinaryDumper();
- $server = stream_socket_server('tcp://127.0.0.1:0');
- Loop::addReadStream($server, function ($server) use ($parser, $dumper) {
- $client = stream_socket_accept($server);
- Loop::addReadStream($client, function ($client) use ($parser, $dumper) {
- Loop::removeReadStream($client);
- $data = fread($client, 512);
- list(, $length) = unpack('n', substr($data, 0, 2));
- assert(strlen($data) - 2 === $length);
- $data = substr($data, 2);
- $message = $parser->parseMessage($data);
- $message->id = 0;
- $data = $dumper->toBinary($message);
- $data = pack('n', strlen($data)) . $data;
- fwrite($client, $data);
- });
- Loop::removeReadStream($server);
- fclose($server);
- });
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $exception = null;
- $executor->query($query)->then(
- null,
- function ($e) use (&$exception) {
- $exception = $e;
- }
- );
- \React\Async\await(\React\Promise\Timer\sleep(0.01));
- if ($exception === null) {
- \React\Async\await(\React\Promise\Timer\sleep(0.2));
- }
- /** @var \RuntimeException $exception */
- $this->assertInstanceOf('RuntimeException', $exception);
- $this->assertEquals('DNS query for google.com (A) failed: Invalid response message received from DNS server tcp://' . $address, $exception->getMessage());
- }
- public function testQueryRejectsIfServerSendsTruncatedResponse()
- {
- $parser = new Parser();
- $dumper = new BinaryDumper();
- $server = stream_socket_server('tcp://127.0.0.1:0');
- Loop::addReadStream($server, function ($server) use ($parser, $dumper) {
- $client = stream_socket_accept($server);
- Loop::addReadStream($client, function ($client) use ($parser, $dumper) {
- Loop::removeReadStream($client);
- $data = fread($client, 512);
- list(, $length) = unpack('n', substr($data, 0, 2));
- assert(strlen($data) - 2 === $length);
- $data = substr($data, 2);
- $message = $parser->parseMessage($data);
- $message->tc = true;
- $data = $dumper->toBinary($message);
- $data = pack('n', strlen($data)) . $data;
- fwrite($client, $data);
- });
- Loop::removeReadStream($server);
- fclose($server);
- });
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $exception = null;
- $executor->query($query)->then(
- null,
- function ($e) use (&$exception) {
- $exception = $e;
- }
- );
- \React\Async\await(\React\Promise\Timer\sleep(0.01));
- if ($exception === null) {
- \React\Async\await(\React\Promise\Timer\sleep(0.2));
- }
- /** @var \RuntimeException $exception */
- $this->assertInstanceOf('RuntimeException', $exception);
- $this->assertEquals('DNS query for google.com (A) failed: Invalid response message received from DNS server tcp://' . $address, $exception->getMessage());
- }
- public function testQueryResolvesIfServerSendsValidResponse()
- {
- $server = stream_socket_server('tcp://127.0.0.1:0');
- Loop::addReadStream($server, function ($server) {
- $client = stream_socket_accept($server);
- Loop::addReadStream($client, function ($client) {
- Loop::removeReadStream($client);
- $data = fread($client, 512);
- list(, $length) = unpack('n', substr($data, 0, 2));
- assert(strlen($data) - 2 === $length);
- fwrite($client, $data);
- });
- Loop::removeReadStream($server);
- fclose($server);
- });
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- $response = \React\Async\await(\React\Promise\Timer\timeout($promise, 0.2));
- $this->assertInstanceOf('React\Dns\Model\Message', $response);
- }
- public function testQueryRejectsIfSocketIsClosedAfterPreviousQueryThatWasStillPending()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->exactly(2))->method('addWriteStream');
- $loop->expects($this->exactly(2))->method('removeWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->once())->method('removeReadStream');
- $loop->expects($this->never())->method('addTimer');
- $loop->expects($this->never())->method('cancelTimer');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise1 = $executor->query($query);
- $client = stream_socket_accept($server);
- $executor->handleWritable();
- // close client socket before processing second write
- fclose($client);
- $promise2 = $executor->query($query);
- $executor->handleWritable();
- $promise1->then(null, $this->expectCallableOnce());
- $promise2->then(null, $this->expectCallableOnce());
- }
- public function testQueryResolvesIfServerSendsBackResponseMessageAndWillStartIdleTimer()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->once())->method('removeWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->never())->method('removeReadStream');
- $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything());
- $loop->expects($this->never())->method('cancelTimer');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- // use outgoing buffer as response message
- $ref = new \ReflectionProperty($executor, 'writeBuffer');
- $ref->setAccessible(true);
- $data = $ref->getValue($executor);
- $client = stream_socket_accept($server);
- fwrite($client, $data);
- $executor->handleWritable();
- $executor->handleRead();
- $promise->then($this->expectCallableOnce());
- }
- public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancellingQueryAndWillStartIdleTimer()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->once())->method('removeWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->never())->method('removeReadStream');
- $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
- $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);
- $loop->expects($this->never())->method('cancelTimer');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- $promise->cancel();
- // use outgoing buffer as response message
- $ref = new \ReflectionProperty($executor, 'writeBuffer');
- $ref->setAccessible(true);
- $data = $ref->getValue($executor);
- $client = stream_socket_accept($server);
- fwrite($client, $data);
- $executor->handleWritable();
- $executor->handleRead();
- //$promise->then(null, $this->expectCallableOnce());
- }
- public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancellingOtherQueryAndWillStartIdleTimer()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->once())->method('removeWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->never())->method('removeReadStream');
- $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything());
- $loop->expects($this->never())->method('cancelTimer');
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- // use outgoing buffer as response message
- $ref = new \ReflectionProperty($executor, 'writeBuffer');
- $ref->setAccessible(true);
- $data = $ref->getValue($executor);
- $client = stream_socket_accept($server);
- fwrite($client, $data);
- $another = $executor->query($query);
- $another->cancel();
- $executor->handleWritable();
- $executor->handleRead();
- $promise->then($this->expectCallableOnce());
- }
- public function testTriggerIdleTimerAfterPreviousQueryResolvedWillCloseIdleSocketConnection()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->once())->method('removeWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->once())->method('removeReadStream');
- $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
- $timerCallback = null;
- $loop->expects($this->once())->method('addTimer')->with(0.001, $this->callback(function ($cb) use (&$timerCallback) {
- $timerCallback = $cb;
- return true;
- }))->willReturn($timer);
- $loop->expects($this->once())->method('cancelTimer')->with($timer);
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- // use outgoing buffer as response message
- $ref = new \ReflectionProperty($executor, 'writeBuffer');
- $ref->setAccessible(true);
- $data = $ref->getValue($executor);
- $client = stream_socket_accept($server);
- fwrite($client, $data);
- $executor->handleWritable();
- $executor->handleRead();
- $promise->then($this->expectCallableOnce());
- // trigger idle timer
- $this->assertNotNull($timerCallback);
- $timerCallback();
- }
- public function testClosingConnectionAfterPreviousQueryResolvedWillCancelIdleTimer()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->once())->method('addWriteStream');
- $loop->expects($this->once())->method('removeWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->once())->method('removeReadStream');
- $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
- $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);
- $loop->expects($this->once())->method('cancelTimer')->with($timer);
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- // use outgoing buffer as response message
- $ref = new \ReflectionProperty($executor, 'writeBuffer');
- $ref->setAccessible(true);
- $data = $ref->getValue($executor);
- $client = stream_socket_accept($server);
- fwrite($client, $data);
- $executor->handleWritable();
- $executor->handleRead();
- $promise->then($this->expectCallableOnce());
- // trigger connection close condition
- fclose($client);
- $executor->handleRead();
- }
- public function testQueryAgainAfterPreviousQueryResolvedWillReuseSocketAndCancelIdleTimer()
- {
- $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
- $loop->expects($this->exactly(2))->method('addWriteStream');
- $loop->expects($this->once())->method('removeWriteStream');
- $loop->expects($this->once())->method('addReadStream');
- $loop->expects($this->never())->method('removeReadStream');
- $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
- $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);
- $loop->expects($this->once())->method('cancelTimer')->with($timer);
- $server = stream_socket_server('tcp://127.0.0.1:0');
- $address = stream_socket_get_name($server, false);
- $executor = new TcpTransportExecutor($address, $loop);
- $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
- $promise = $executor->query($query);
- // use outgoing buffer as response message
- $ref = new \ReflectionProperty($executor, 'writeBuffer');
- $ref->setAccessible(true);
- $data = $ref->getValue($executor);
- $client = stream_socket_accept($server);
- fwrite($client, $data);
- $executor->handleWritable();
- $executor->handleRead();
- $promise->then($this->expectCallableOnce());
- // trigger second query
- $executor->query($query);
- }
- }
|