1414use \UnexpectedValueException ;
1515use \InvalidArgumentException ;
1616use \Exception ;
17+ use React \Promise \Timer \TimeoutException ;
1718
1819class Server extends EventEmitter
1920{
21+ // the following error codes are only used for SOCKS5 only
22+ /** @internal */
23+ const ERROR_GENERAL = 0x01 ;
24+ /** @internal */
25+ const ERROR_NOT_ALLOWED_BY_RULESET = 0x02 ;
26+ /** @internal */
27+ const ERROR_NETWORK_UNREACHABLE = 0x03 ;
28+ /** @internal */
29+ const ERROR_HOST_UNREACHABLE = 0x04 ;
30+ /** @internal */
31+ const ERROR_CONNECTION_REFUSED = 0x05 ;
32+ /** @internal */
33+ const ERROR_TTL = 0x06 ;
34+ /** @internal */
35+ const ERROR_COMMAND_UNSUPPORTED = 0x07 ;
36+ /** @internal */
37+ const ERROR_ADDRESS_UNSUPPORTED = 0x08 ;
38+
2039 protected $ loop ;
2140
2241 private $ connector ;
@@ -274,7 +293,7 @@ public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamRead
274293 });
275294 } else {
276295 // reject all offered authentication methods
277- $ stream ->end (pack ('C2 ' , 0x05 , 0xFF ));
296+ $ stream ->write (pack ('C2 ' , 0x05 , 0xFF ));
278297 throw new UnexpectedValueException ('No acceptable authentication mechanism found ' );
279298 }
280299 })->then (function ($ method ) use ($ reader , $ stream ) {
@@ -289,7 +308,7 @@ public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamRead
289308 throw new UnexpectedValueException ('Invalid SOCKS version ' );
290309 }
291310 if ($ data ['command ' ] !== 0x01 ) {
292- throw new UnexpectedValueException ('Only CONNECT requests supported ' );
311+ throw new UnexpectedValueException ('Only CONNECT requests supported ' , Server:: ERROR_COMMAND_UNSUPPORTED );
293312 }
294313// if ($data['null'] !== 0x00) {
295314// throw new UnexpectedValueException('Reserved byte has to be NULL');
@@ -310,7 +329,7 @@ public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamRead
310329 return inet_ntop ($ addr );
311330 });
312331 } else {
313- throw new UnexpectedValueException ('Invalid target type ' );
332+ throw new UnexpectedValueException ('Invalid address type ' , Server:: ERROR_ADDRESS_UNSUPPORTED );
314333 }
315334 })->then (function ($ host ) use ($ reader , &$ remote ) {
316335 return $ reader ->readBinary (array ('port ' =>'n ' ))->then (function ($ data ) use ($ host , &$ remote ) {
@@ -319,14 +338,13 @@ public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamRead
319338 })->then (function ($ target ) use ($ that , $ stream ) {
320339 return $ that ->connectTarget ($ stream , $ target );
321340 }, function ($ error ) use ($ stream ) {
322- throw new UnexpectedValueException ('SOCKS5 protocol error ' ,0 , $ error );
341+ throw new UnexpectedValueException ('SOCKS5 protocol error ' , $ error -> getCode (), $ error );
323342 })->then (function (ConnectionInterface $ remote ) use ($ stream ) {
324343 $ stream ->write (pack ('C4Nn ' , 0x05 , 0x00 , 0x00 , 0x01 , 0 , 0 ));
325344
326345 return $ remote ;
327346 }, function (Exception $ error ) use ($ stream ){
328- $ code = 0x01 ;
329- $ stream ->end (pack ('C4Nn ' , 0x05 , $ code , 0x00 , 0x01 , 0 , 0 ));
347+ $ stream ->write (pack ('C4Nn ' , 0x05 , $ error ->getCode () === 0 ? Server::ERROR_GENERAL : $ error ->getCode (), 0x00 , 0x01 , 0 , 0 ));
330348
331349 throw $ error ;
332350 });
@@ -378,7 +396,25 @@ public function connectTarget(ConnectionInterface $stream, array $target)
378396
379397 return $ remote ;
380398 }, function (Exception $ error ) {
381- throw new UnexpectedValueException ('Unable to connect to remote target ' , 0 , $ error );
399+ // default to general/unknown error
400+ $ code = Server::ERROR_GENERAL ;
401+
402+ // map common socket error conditions to limited list of SOCKS error codes
403+ if ((defined ('SOCKET_EACCES ' ) && $ error ->getCode () === SOCKET_EACCES ) || $ error ->getCode () === 13 ) {
404+ $ code = Server::ERROR_NOT_ALLOWED_BY_RULESET ;
405+ } elseif ((defined ('SOCKET_EHOSTUNREACH ' ) && $ error ->getCode () === SOCKET_EHOSTUNREACH ) || $ error ->getCode () === 113 ) {
406+ $ code = Server::ERROR_HOST_UNREACHABLE ;
407+ } elseif ((defined ('SOCKET_ENETUNREACH ' ) && $ error ->getCode () === SOCKET_ENETUNREACH ) || $ error ->getCode () === 101 ) {
408+ $ code = Server::ERROR_NETWORK_UNREACHABLE ;
409+ } elseif ((defined ('SOCKET_ECONNREFUSED ' ) && $ error ->getCode () === SOCKET_ECONNREFUSED ) || $ error ->getCode () === 111 || $ error ->getMessage () === 'Connection refused ' ) {
410+ // Socket component does not currently assign an error code for this, so we have to resort to checking the exception message
411+ $ code = Server::ERROR_CONNECTION_REFUSED ;
412+ } elseif ((defined ('SOCKET_ETIMEDOUT ' ) && $ error ->getCode () === SOCKET_ETIMEDOUT ) || $ error ->getCode () === 110 || $ error instanceof TimeoutException) {
413+ // Socket component does not currently assign an error code for this, but we can rely on the TimeoutException
414+ $ code = Server::ERROR_TTL ;
415+ }
416+
417+ throw new UnexpectedValueException ('Unable to connect to remote target ' , $ code , $ error );
382418 });
383419 }
384420}
0 commit comments