Skip to content

Commit ace3394

Browse files
authored
Merge pull request #83 from clue-labs/error-messages
Improve error reporting by always including target URI in exceptions
2 parents 9f88aa6 + bb37efc commit ace3394

File tree

3 files changed

+238
-74
lines changed

3 files changed

+238
-74
lines changed

src/Client.php

Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,9 @@ public function connect($uri)
160160
// start TCP/IP connection to SOCKS server
161161
$connecting = $this->connector->connect($socksUri);
162162

163-
$deferred = new Deferred(function ($_, $reject) use ($connecting) {
163+
$deferred = new Deferred(function ($_, $reject) use ($uri, $connecting) {
164164
$reject(new RuntimeException(
165-
'Connection cancelled while waiting for proxy (ECONNABORTED)',
165+
'Connection to ' . $uri . ' cancelled while waiting for proxy (ECONNABORTED)',
166166
defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
167167
));
168168

@@ -177,12 +177,12 @@ public function connect($uri)
177177
// resolve plain connection once SOCKS protocol is completed
178178
$that = $this;
179179
$connecting->then(
180-
function (ConnectionInterface $stream) use ($that, $host, $port, $deferred) {
181-
$that->handleConnectedSocks($stream, $host, $port, $deferred);
180+
function (ConnectionInterface $stream) use ($that, $host, $port, $deferred, $uri) {
181+
$that->handleConnectedSocks($stream, $host, $port, $deferred, $uri);
182182
},
183-
function (Exception $e) use ($deferred) {
183+
function (Exception $e) use ($uri, $deferred) {
184184
$deferred->reject($e = new RuntimeException(
185-
'Connection failed because connection to proxy failed (ECONNREFUSED)',
185+
'Connection to ' . $uri . ' failed because connection to proxy failed (ECONNREFUSED)',
186186
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111,
187187
$e
188188
));
@@ -213,26 +213,33 @@ function (Exception $e) use ($deferred) {
213213
* @param string $host
214214
* @param int $port
215215
* @param Deferred $deferred
216+
* @param string $uri
216217
* @return void
217218
* @internal
218219
*/
219-
public function handleConnectedSocks(ConnectionInterface $stream, $host, $port, Deferred $deferred)
220+
public function handleConnectedSocks(ConnectionInterface $stream, $host, $port, Deferred $deferred, $uri)
220221
{
221222
$reader = new StreamReader();
222223
$stream->on('data', array($reader, 'write'));
223224

224-
$stream->on('error', $onError = function (Exception $e) use ($deferred) {
225-
$deferred->reject(new RuntimeException('Stream error while waiting for response from proxy (EIO)', defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e));
225+
$stream->on('error', $onError = function (Exception $e) use ($deferred, $uri) {
226+
$deferred->reject(new RuntimeException(
227+
'Connection to ' . $uri . ' failed because connection to proxy caused a stream error (EIO)',
228+
defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e)
229+
);
226230
});
227231

228-
$stream->on('close', $onClose = function () use ($deferred) {
229-
$deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
232+
$stream->on('close', $onClose = function () use ($deferred, $uri) {
233+
$deferred->reject(new RuntimeException(
234+
'Connection to ' . $uri . ' failed because connection to proxy was lost while waiting for response from proxy (ECONNRESET)',
235+
defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104)
236+
);
230237
});
231238

232239
if ($this->protocolVersion === 5) {
233-
$promise = $this->handleSocks5($stream, $host, $port, $reader);
240+
$promise = $this->handleSocks5($stream, $host, $port, $reader, $uri);
234241
} else {
235-
$promise = $this->handleSocks4($stream, $host, $port, $reader);
242+
$promise = $this->handleSocks4($stream, $host, $port, $reader, $uri);
236243
}
237244

238245
$promise->then(function () use ($deferred, $stream, $reader, $onError, $onClose) {
@@ -241,18 +248,22 @@ public function handleConnectedSocks(ConnectionInterface $stream, $host, $port,
241248
$stream->removeListener('close', $onClose);
242249

243250
$deferred->resolve($stream);
244-
}, function (Exception $error) use ($deferred, $stream) {
251+
}, function (Exception $error) use ($deferred, $stream, $uri) {
245252
// pass custom RuntimeException through as-is, otherwise wrap in protocol error
246253
if (!$error instanceof RuntimeException) {
247-
$error = new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $error);
254+
$error = new RuntimeException(
255+
'Connection to ' . $uri . ' failed because proxy returned invalid response (EBADMSG)',
256+
defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71,
257+
$error
258+
);
248259
}
249260

250261
$deferred->reject($error);
251262
$stream->close();
252263
});
253264
}
254265

255-
private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader)
266+
private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader, $uri)
256267
{
257268
// do not resolve hostname. only try to convert to IP
258269
$ip = ip2long($host);
@@ -272,17 +283,20 @@ private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamR
272283
'status' => 'C',
273284
'port' => 'n',
274285
'ip' => 'N'
275-
))->then(function ($data) {
286+
))->then(function ($data) use ($uri) {
276287
if ($data['null'] !== 0x00) {
277288
throw new Exception('Invalid SOCKS response');
278289
}
279290
if ($data['status'] !== 0x5a) {
280-
throw new RuntimeException('Proxy refused connection with SOCKS error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
291+
throw new RuntimeException(
292+
'Connection to ' . $uri . ' failed because proxy refused connection with error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)',
293+
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
294+
);
281295
}
282296
});
283297
}
284298

285-
private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader)
299+
private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader, $uri)
286300
{
287301
// protocol version 5
288302
$data = pack('C', 0x05);
@@ -302,7 +316,7 @@ private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamR
302316
return $reader->readBinary(array(
303317
'version' => 'C',
304318
'method' => 'C'
305-
))->then(function ($data) use ($auth, $stream, $reader) {
319+
))->then(function ($data) use ($auth, $stream, $reader, $uri) {
306320
if ($data['version'] !== 0x05) {
307321
throw new Exception('Version/Protocol mismatch');
308322
}
@@ -314,14 +328,20 @@ private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamR
314328
return $reader->readBinary(array(
315329
'version' => 'C',
316330
'status' => 'C'
317-
))->then(function ($data) {
331+
))->then(function ($data) use ($uri) {
318332
if ($data['version'] !== 0x01 || $data['status'] !== 0x00) {
319-
throw new RuntimeException('Username/Password authentication failed (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
333+
throw new RuntimeException(
334+
'Connection to ' . $uri . ' failed because proxy denied access with given authentication details (EACCES)',
335+
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
336+
);
320337
}
321338
});
322339
} else if ($data['method'] !== 0x00) {
323340
// any other method than "no authentication"
324-
throw new RuntimeException('No acceptable authentication method found (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
341+
throw new RuntimeException(
342+
'Connection to ' . $uri . ' failed because proxy denied access due to unsupported authentication method (EACCES)',
343+
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
344+
);
325345
}
326346
})->then(function () use ($stream, $reader, $host, $port) {
327347
// do not resolve hostname. only try to convert to (binary/packed) IP
@@ -345,32 +365,59 @@ private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamR
345365
'null' => 'C',
346366
'type' => 'C'
347367
));
348-
})->then(function ($data) use ($reader) {
368+
})->then(function ($data) use ($reader, $uri) {
349369
if ($data['version'] !== 0x05 || $data['null'] !== 0x00) {
350370
throw new Exception('Invalid SOCKS response');
351371
}
352372
if ($data['status'] !== 0x00) {
353373
// map limited list of SOCKS error codes to common socket error conditions
354374
// @link https://tools.ietf.org/html/rfc1928#section-6
355375
if ($data['status'] === Server::ERROR_GENERAL) {
356-
throw new RuntimeException('SOCKS server reported a general server failure (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
376+
throw new RuntimeException(
377+
'Connection to ' . $uri . ' failed because proxy refused connection with general server failure (ECONNREFUSED)',
378+
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
379+
);
357380
} elseif ($data['status'] === Server::ERROR_NOT_ALLOWED_BY_RULESET) {
358-
throw new RuntimeException('SOCKS server reported connection is not allowed by ruleset (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
381+
throw new RuntimeException(
382+
'Connection to ' . $uri . ' failed because proxy denied access due to ruleset (EACCES)',
383+
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
384+
);
359385
} elseif ($data['status'] === Server::ERROR_NETWORK_UNREACHABLE) {
360-
throw new RuntimeException('SOCKS server reported network unreachable (ENETUNREACH)', defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101);
386+
throw new RuntimeException(
387+
'Connection to ' . $uri . ' failed because proxy reported network unreachable (ENETUNREACH)',
388+
defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101
389+
);
361390
} elseif ($data['status'] === Server::ERROR_HOST_UNREACHABLE) {
362-
throw new RuntimeException('SOCKS server reported host unreachable (EHOSTUNREACH)', defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113);
391+
throw new RuntimeException(
392+
'Connection to ' . $uri . ' failed because proxy reported host unreachable (EHOSTUNREACH)',
393+
defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113
394+
);
363395
} elseif ($data['status'] === Server::ERROR_CONNECTION_REFUSED) {
364-
throw new RuntimeException('SOCKS server reported connection refused (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
396+
throw new RuntimeException(
397+
'Connection to ' . $uri . ' failed because proxy reported connection refused (ECONNREFUSED)',
398+
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
399+
);
365400
} elseif ($data['status'] === Server::ERROR_TTL) {
366-
throw new RuntimeException('SOCKS server reported TTL/timeout expired (ETIMEDOUT)', defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110);
401+
throw new RuntimeException(
402+
'Connection to ' . $uri . ' failed because proxy reported TTL/timeout expired (ETIMEDOUT)',
403+
defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110
404+
);
367405
} elseif ($data['status'] === Server::ERROR_COMMAND_UNSUPPORTED) {
368-
throw new RuntimeException('SOCKS server does not support the CONNECT command (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
406+
throw new RuntimeException(
407+
'Connection to ' . $uri . ' failed because proxy does not support the CONNECT command (EPROTO)',
408+
defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71
409+
);
369410
} elseif ($data['status'] === Server::ERROR_ADDRESS_UNSUPPORTED) {
370-
throw new RuntimeException('SOCKS server does not support this address type (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
411+
throw new RuntimeException(
412+
'Connection to ' . $uri . ' failed because proxy does not support this address type (EPROTO)',
413+
defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71
414+
);
371415
}
372416

373-
throw new RuntimeException('SOCKS server reported an unassigned error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
417+
throw new RuntimeException(
418+
'Connection to ' . $uri . ' failed because proxy server refused connection with unknown error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)',
419+
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
420+
);
374421
}
375422
if ($data['type'] === 0x01) {
376423
// IPv4 address => skip IP and port

0 commit comments

Comments
 (0)