Skip to content

Commit 07ca854

Browse files
committed
Minor refactoring to simplify testing UDP write errors
1 parent d8f748f commit 07ca854

File tree

2 files changed

+24
-9
lines changed

2 files changed

+24
-9
lines changed

src/Query/UdpTransportExecutor.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ final class UdpTransportExecutor implements ExecutorInterface
9292
private $parser;
9393
private $dumper;
9494

95+
/**
96+
* maximum UDP packet size to send and receive
97+
*
98+
* @var int
99+
*/
100+
private $maxPacketSize = 512;
101+
95102
/**
96103
* @param string $nameserver
97104
* @param LoopInterface $loop
@@ -119,7 +126,7 @@ public function query(Query $query)
119126
$request = Message::createRequestForQuery($query);
120127

121128
$queryData = $this->dumper->toBinary($request);
122-
if (isset($queryData[512])) {
129+
if (isset($queryData[$this->maxPacketSize])) {
123130
return \React\Promise\reject(new \RuntimeException(
124131
'DNS query for ' . $query->name . ' failed: Query too large for UDP transport',
125132
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
@@ -142,14 +149,14 @@ public function query(Query $query)
142149
if ($written !== \strlen($queryData)) {
143150
// Write may potentially fail, but most common errors are already caught by connection check above.
144151
// Among others, macOS is known to report here when trying to send to broadcast address.
145-
// @codeCoverageIgnoreStart
152+
// This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data.
153+
// fwrite(): send of 8192 bytes failed with errno=111 Connection refused
146154
$error = \error_get_last();
147155
\preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
148156
return \React\Promise\reject(new \RuntimeException(
149157
'DNS query for ' . $query->name . ' failed: Unable to send query to DNS server (' . (isset($m[2]) ? $m[2] : $error['message']) . ')',
150158
isset($m[1]) ? (int) $m[1] : 0
151159
));
152-
// @codeCoverageIgnoreEnd
153160
}
154161

155162
$loop = $this->loop;
@@ -161,11 +168,12 @@ public function query(Query $query)
161168
throw new CancellationException('DNS query for ' . $query->name . ' has been cancelled');
162169
});
163170

171+
$max = $this->maxPacketSize;
164172
$parser = $this->parser;
165-
$loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request) {
173+
$loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request, $max) {
166174
// try to read a single data packet from the DNS server
167175
// ignoring any errors, this is uses UDP packets and not a stream of data
168-
$data = @\fread($socket, 512);
176+
$data = @\fread($socket, $max);
169177

170178
try {
171179
$response = $parser->parseMessage($data);

tests/Query/UdpTransportExecutorTest.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ public function testQueryRejectsIfServerConnectionFails()
126126
$exception = $reason;
127127
});
128128

129-
$this->setExpectedException('RuntimeException', 'Unable to connect to DNS server (Failed to parse address "///")');
129+
// PHP (Failed to parse address "///") differs from HHVM (Name or service not known)
130+
$this->setExpectedException('RuntimeException', 'Unable to connect to DNS server');
130131
throw $exception;
131132
}
132133

@@ -135,9 +136,14 @@ public function testQueryRejectsIfSendToServerFailsAfterConnection()
135136
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
136137
$loop->expects($this->never())->method('addReadStream');
137138

138-
$executor = new UdpTransportExecutor('255.255.255.255', $loop);
139+
$executor = new UdpTransportExecutor('0.0.0.0', $loop);
139140

140-
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
141+
// increase hard-coded maximum packet size to allow sending excessive data
142+
$ref = new \ReflectionProperty($executor, 'maxPacketSize');
143+
$ref->setAccessible(true);
144+
$ref->setValue($executor, PHP_INT_MAX);
145+
146+
$query = new Query(str_repeat('a.', 100000) . '.example', Message::TYPE_A, Message::CLASS_IN);
141147
$promise = $executor->query($query);
142148

143149
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
@@ -147,7 +153,8 @@ public function testQueryRejectsIfSendToServerFailsAfterConnection()
147153
$exception = $reason;
148154
});
149155

150-
$this->setExpectedException('RuntimeException', 'to DNS server (Permission denied)', SOCKET_EACCES);
156+
// ECONNREFUSED( Connection refused) on Linux, EMSGSIZE (Message too long) on macOS
157+
$this->setExpectedException('RuntimeException', 'Unable to send query to DNS server');
151158
throw $exception;
152159
}
153160

0 commit comments

Comments
 (0)