Update
This commit is contained in:
parent
c8ed5971d9
commit
89bb80285b
@ -96,7 +96,7 @@ class API extends InternalDoc
|
||||
if ($e->getFile() === 'MadelineProto' && $e->getLine() === 1) {
|
||||
throw $e;
|
||||
}
|
||||
if (\defined('MADELINEPROTO_TEST') && MADELINEPROTO_TEST === 'pony') {
|
||||
if (\MADELINEPROTO_TEST === 'pony') {
|
||||
throw $e;
|
||||
}
|
||||
\class_exists('\\Volatile');
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DataCenter DoH proxying AMPHP connector.
|
||||
* Proxying AMPHP connector.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
@ -18,152 +18,56 @@
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\CancellationToken;
|
||||
use Amp\MultiReasonException;
|
||||
use Amp\NullCancellationToken;
|
||||
use Amp\Promise;
|
||||
use Amp\Socket\ConnectContext;
|
||||
use Amp\Socket\Connector;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
|
||||
class ContextConnector implements Connector
|
||||
{
|
||||
/**
|
||||
* Datacenter instance
|
||||
*
|
||||
* @property DataCenter $dataCenter
|
||||
*/
|
||||
private $dataCenter;
|
||||
/**
|
||||
* Connection context
|
||||
*
|
||||
* @var ConnectionContext
|
||||
*/
|
||||
private $ctx;
|
||||
public function __construct(DataCenter $dataCenter, ConnectionContext $ctx)
|
||||
private $fromDns = false;
|
||||
public function __construct(DataCenter $dataCenter, bool $fromDns = false)
|
||||
{
|
||||
$this->dataCenter = $dataCenter;
|
||||
$this->ctx = $ctx;
|
||||
$this->fromDns = false;
|
||||
}
|
||||
|
||||
public function connect(string $uri, ?ConnectContext $socketContext = null, ?CancellationToken $token = null): Promise
|
||||
public function connect(string $uri, ?ConnectContext $ctx = null, ?CancellationToken $token = null): Promise
|
||||
{
|
||||
return Tools::call(function () use ($uri, $socketContext, $token) {
|
||||
$socketContext = $socketContext ?? new ConnectContext;
|
||||
return Tools::call(function () use ($uri, $ctx, $token) {
|
||||
$ctx = $ctx ?? new ConnectContext;
|
||||
$token = $token ?? new NullCancellationToken;
|
||||
|
||||
$attempt = 0;
|
||||
$uris = [];
|
||||
$failures = [];
|
||||
[$scheme, $host, $port] = parseUri($uri);
|
||||
if ($host[0] === '[') {
|
||||
$host = \substr($host, 1, -1);
|
||||
$ctxs = $this->datacenter->generateContexts(0, $uri, $ctx);
|
||||
if (empty($ctxs)) {
|
||||
throw new Exception("No contexts for raw connection to URI $uri");
|
||||
}
|
||||
if ($port === 0 || @\inet_pton($host)) {
|
||||
// Host is already an IP address or file path.
|
||||
$uris = [$uri];
|
||||
} else {
|
||||
// Host is not an IP address, so resolve the domain name.
|
||||
// When we're connecting to a host, we may need to resolve the domain name, first.
|
||||
// The resolution is usually done using DNS over HTTPS.
|
||||
//
|
||||
// The DNS over HTTPS resolver needs to resolve the domain name of the DOH server:
|
||||
// this is handled internally by the DNS over HTTPS client,
|
||||
// by redirecting the resolution request to the plain DNS client.
|
||||
//
|
||||
// However, if the DoH connection is proxied with a proxy that has a domain name itself,
|
||||
// we cannot resolve it with the DoH resolver, since this will cause an infinite loop
|
||||
//
|
||||
// resolve host.com => (DoH resolver) => resolve dohserver.com => (simple resolver) => OK
|
||||
//
|
||||
// |> resolve dohserver.com => (simple resolver) => OK
|
||||
// resolve host.com => (DoH resolver) =|
|
||||
// |> resolve proxy.com => (non-proxied resolver) => OK
|
||||
//
|
||||
//
|
||||
// This means that we must detect if the domain name we're trying to resolve is a proxy domain name.
|
||||
//
|
||||
// Here, we simply check if the connection URI has changed since we first set it:
|
||||
// this would indicate that a proxy class has changed the connection URI to the proxy URI.
|
||||
//
|
||||
if ($this->ctx->isDns()) {
|
||||
$records = yield $this->dataCenter->getNonProxiedDNSClient()->resolve($host, $socketContext->getDnsTypeRestriction());
|
||||
} else {
|
||||
$records = yield $this->dataCenter->getDNSClient()->resolve($host, $socketContext->getDnsTypeRestriction());
|
||||
}
|
||||
\usort($records, function (Record $a, Record $b) {
|
||||
return $a->getType() - $b->getType();
|
||||
});
|
||||
if ($this->ctx->getIpv6()) {
|
||||
$records = \array_reverse($records);
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
/** @var Record $record */
|
||||
if ($record->getType() === Record::AAAA) {
|
||||
$uris[] = \sprintf("%s://[%s]:%d", $scheme, $record->getValue(), $port);
|
||||
} else {
|
||||
$uris[] = \sprintf("%s://%s:%d", $scheme, $record->getValue(), $port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$flags = \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT;
|
||||
$timeout = $socketContext->getConnectTimeout();
|
||||
foreach ($uris as $builtUri) {
|
||||
foreach ($ctxs as $ctx) {
|
||||
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
try {
|
||||
$streamContext = \stream_context_create($socketContext->withoutTlsContext()->toStreamContextArray());
|
||||
if (!$socket = @\stream_socket_client($builtUri, $errno, $errstr, null, $flags, $streamContext)) {
|
||||
throw new ConnectException(\sprintf(
|
||||
'Connection to %s failed: [Error #%d] %s%s',
|
||||
$uri,
|
||||
$errno,
|
||||
$errstr,
|
||||
$failures ? '; previous attempts: ' . \implode($failures) : ''
|
||||
), $errno);
|
||||
$ctx->setIsDns($this->fromDns);
|
||||
$ctx->setCancellationToken($token);
|
||||
$result = yield $ctx->getStream();
|
||||
$this->API->logger->logger('OK!', \danog\MadelineProto\Logger::WARNING);
|
||||
|
||||
return $result->getSocket();
|
||||
} catch (\Throwable $e) {
|
||||
if (\MADELINEPROTO_TEST === 'pony') {
|
||||
throw $e;
|
||||
}
|
||||
\stream_set_blocking($socket, false);
|
||||
$deferred = new Deferred;
|
||||
$watcher = Loop::onWritable($socket, [$deferred, 'resolve']);
|
||||
$id = $token->subscribe([$deferred, 'fail']);
|
||||
try {
|
||||
yield Promise\timeout($deferred->promise(), $timeout);
|
||||
} catch (TimeoutException $e) {
|
||||
throw new ConnectException(\sprintf(
|
||||
'Connecting to %s failed: timeout exceeded (%d ms)%s',
|
||||
$uri,
|
||||
$timeout,
|
||||
$failures ? '; previous attempts: ' . \implode($failures) : ''
|
||||
), 110); // See ETIMEDOUT in http://www.virtsync.com/c-error-codes-include-errno
|
||||
} finally {
|
||||
Loop::cancel($watcher);
|
||||
$token->unsubscribe($id);
|
||||
$this->API->logger->logger('Connection failed: '.$e, \danog\MadelineProto\Logger::ERROR);
|
||||
if ($e instanceof MultiReasonException) {
|
||||
foreach ($e->getReasons() as $reason) {
|
||||
$this->API->logger->logger('Multireason: '.$reason, \danog\MadelineProto\Logger::ERROR);
|
||||
}
|
||||
}
|
||||
// The following hack looks like the only way to detect connection refused errors with PHP's stream sockets.
|
||||
if (\stream_socket_get_name($socket, true) === false) {
|
||||
\fclose($socket);
|
||||
throw new ConnectException(\sprintf(
|
||||
'Connection to %s refused%s',
|
||||
$uri,
|
||||
$failures ? '; previous attempts: ' . \implode($failures) : ''
|
||||
), 111); // See ECONNREFUSED in http://www.virtsync.com/c-error-codes-include-errno
|
||||
}
|
||||
} catch (ConnectException $e) {
|
||||
// Includes only error codes used in this file, as error codes on other OS families might be different.
|
||||
// In fact, this might show a confusing error message on OS families that return 110 or 111 by itself.
|
||||
$knownReasons = [
|
||||
110 => 'connection timeout',
|
||||
111 => 'connection refused',
|
||||
];
|
||||
$code = $e->getCode();
|
||||
$reason = $knownReasons[$code] ?? ('Error #' . $code);
|
||||
if (++$attempt === $socketContext->getMaxAttempts()) {
|
||||
break;
|
||||
}
|
||||
$failures[] = "{$uri} ({$reason})";
|
||||
continue; // Could not connect to host, try next host in the list.
|
||||
}
|
||||
return ResourceSocket::fromClientSocket($socket, $socketContext->getTlsContext());
|
||||
}
|
||||
// This is reached if either all URIs failed or the maximum number of attempts is reached.
|
||||
/** @noinspection PhpUndefinedVariableInspection */
|
||||
throw $e;
|
||||
|
||||
throw new \danog\MadelineProto\Exception("Could not connect to URI $uri");
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -19,9 +19,6 @@
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\CancellationToken;
|
||||
use Amp\Deferred;
|
||||
use Amp\Dns\Record;
|
||||
use Amp\Dns\Resolver;
|
||||
use Amp\Dns\Rfc1035StubResolver;
|
||||
use Amp\DoH\DoHConfig;
|
||||
@ -34,15 +31,8 @@ use Amp\Http\Client\Cookie\CookieJar;
|
||||
use Amp\Http\Client\Cookie\InMemoryCookieJar;
|
||||
use Amp\Http\Client\DelegateHttpClient;
|
||||
use Amp\Http\Client\HttpClientBuilder;
|
||||
use Amp\Loop;
|
||||
use Amp\MultiReasonException;
|
||||
use Amp\NullCancellationToken;
|
||||
use Amp\Promise;
|
||||
use Amp\Socket\ClientSocket;
|
||||
use Amp\Socket\ClientTlsContext;
|
||||
use Amp\Socket\ConnectContext;
|
||||
use Amp\Socket\ConnectException;
|
||||
use Amp\TimeoutException;
|
||||
use Amp\Websocket\Client\Rfc6455Connector;
|
||||
use danog\MadelineProto\MTProto\PermAuthKey;
|
||||
use danog\MadelineProto\MTProto\TempAuthKey;
|
||||
use danog\MadelineProto\Stream\Common\BufferedRawStream;
|
||||
@ -60,8 +50,6 @@ use danog\MadelineProto\Stream\StreamInterface;
|
||||
use danog\MadelineProto\Stream\Transport\DefaultStream;
|
||||
use danog\MadelineProto\Stream\Transport\WssStream;
|
||||
use danog\MadelineProto\Stream\Transport\WsStream;
|
||||
use function Amp\call;
|
||||
use function Amp\Socket\Internal\parseUri;
|
||||
|
||||
/**
|
||||
* Manages datacenters.
|
||||
@ -117,7 +105,7 @@ class DataCenter
|
||||
*
|
||||
* @var \Amp\DoH\Rfc8484StubResolver
|
||||
*/
|
||||
private $NonProxiedDoHClient;
|
||||
private $nonProxiedDoHClient;
|
||||
/**
|
||||
* Cookie jar.
|
||||
*
|
||||
@ -231,12 +219,12 @@ class DataCenter
|
||||
$this->CookieJar = $jar ?? new InMemoryCookieJar;
|
||||
$this->HTTPClient = (new HttpClientBuilder)
|
||||
->interceptNetwork(new CookieInterceptor($this->CookieJar))
|
||||
->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(new ProxyConnector($this))))
|
||||
->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(new ContextConnector($this))))
|
||||
->build();
|
||||
|
||||
$DoHHTTPClient = (new HttpClientBuilder)
|
||||
->interceptNetwork(new CookieInterceptor($this->CookieJar))
|
||||
->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(new ProxyConnector($this, true))))
|
||||
->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(new ContextConnector($this, true))))
|
||||
->build();
|
||||
|
||||
$DoHConfig = new DoHConfig(
|
||||
@ -246,19 +234,19 @@ class DataCenter
|
||||
],
|
||||
$DoHHTTPClient
|
||||
);
|
||||
$NonProxiedDoHConfig = new DoHConfig(
|
||||
$nonProxiedDoHConfig = new DoHConfig(
|
||||
[
|
||||
new Nameserver('https://mozilla.cloudflare-dns.com/dns-query'),
|
||||
new Nameserver('https://dns.google/resolve'),
|
||||
]
|
||||
);
|
||||
$this->DoHClient = Magic::$altervista || Magic::$zerowebhost ?
|
||||
new Rfc1035StubResolver() :
|
||||
$this->DoHClient = Magic::$altervista || Magic::$zerowebhost ?
|
||||
new Rfc1035StubResolver() :
|
||||
new Rfc8484StubResolver($DoHConfig);
|
||||
|
||||
$this->NonProxiedDoHClient = Magic::$altervista || Magic::$zerowebhost ?
|
||||
new Rfc1035StubResolver() :
|
||||
new Rfc8484StubResolver($NonProxiedDoHConfig);
|
||||
$this->nonProxiedDoHClient = Magic::$altervista || Magic::$zerowebhost ?
|
||||
new Rfc1035StubResolver() :
|
||||
new Rfc8484StubResolver($nonProxiedDoHConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,12 +285,10 @@ class DataCenter
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
if (\defined(\MADELINEPROTO_TEST::class) && MADELINEPROTO_TEST === 'pony') {
|
||||
if (\MADELINEPROTO_TEST === 'pony') {
|
||||
throw $e;
|
||||
}
|
||||
$this->API->logger->logger('Connection failed: '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
|
||||
} catch (\Exception $e) {
|
||||
$this->API->logger->logger('Connection failed: '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@ -454,7 +440,7 @@ class DataCenter
|
||||
foreach ($ipv6 as $ipv6) {
|
||||
// This is only for non-MTProto connections
|
||||
if (!$dc_number) {
|
||||
/** @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
$ctx = (new ConnectionContext())
|
||||
->setSocketContext($context)
|
||||
->setUri($uri)
|
||||
@ -462,15 +448,7 @@ class DataCenter
|
||||
|
||||
foreach ($combo as $stream) {
|
||||
if ($stream[0] === DefaultStream::getName() && $stream[1] === []) {
|
||||
$stream[1] = [
|
||||
function (
|
||||
string $uri,
|
||||
ClientConnectContext $socketContext = null,
|
||||
CancellationToken $token = null
|
||||
) use ($ctx): Promise {
|
||||
return $this->socketConnect($ctx, $uri, $socketContext, $token);
|
||||
}
|
||||
];
|
||||
$stream[1] = new DoHConnector($this, $ctx);
|
||||
}
|
||||
$ctx->addStream(...$stream);
|
||||
}
|
||||
@ -522,7 +500,7 @@ class DataCenter
|
||||
$uri = 'tcp://'.$address.':'.$port.'/'.$path;
|
||||
}
|
||||
|
||||
/** @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
$ctx = (new ConnectionContext())
|
||||
->setDc($dc_number)
|
||||
->setTest($this->settings[$dc_config_number]['test_mode'])
|
||||
@ -532,7 +510,20 @@ class DataCenter
|
||||
|
||||
foreach ($combo as $stream) {
|
||||
if ($stream[0] === DefaultStream::getName() && $stream[1] === []) {
|
||||
$stream[1] = new ContextConnector($this, $ctx);
|
||||
$stream[1] = new DoHConnector($this, $ctx);
|
||||
}
|
||||
if (\in_array($stream[0], [WsStream::class, WssStream::class]) && $stream[1] === []) {
|
||||
$stream[1] = new Rfc6455Connector(
|
||||
(new HttpClientBuilder)
|
||||
->usingPool(
|
||||
new UnlimitedConnectionPool(
|
||||
new DefaultConnectionFactory(
|
||||
new DoHConnector($this, $ctx)
|
||||
)
|
||||
)
|
||||
)
|
||||
->build()
|
||||
);
|
||||
}
|
||||
$ctx->addStream(...$stream);
|
||||
}
|
||||
@ -549,7 +540,7 @@ class DataCenter
|
||||
unset($this->sockets[$dc_number]);
|
||||
|
||||
$this->API->logger->logger("No info for DC $dc_number", \danog\MadelineProto\Logger::ERROR);
|
||||
} elseif (\defined('MADELINEPROTO_TEST') && MADELINEPROTO_TEST === 'pony') {
|
||||
} elseif (\MADELINEPROTO_TEST === 'pony') {
|
||||
return [$ctxs[0]];
|
||||
}
|
||||
|
||||
|
182
src/danog/MadelineProto/DoHConnector.php
Normal file
182
src/danog/MadelineProto/DoHConnector.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DataCenter DoH proxying AMPHP connector.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\CancellationToken;
|
||||
use Amp\Deferred;
|
||||
use Amp\Dns\Record;
|
||||
use Amp\Dns\TimeoutException;
|
||||
use Amp\Loop;
|
||||
use Amp\NullCancellationToken;
|
||||
use Amp\Promise;
|
||||
use Amp\Socket\ConnectContext;
|
||||
use Amp\Socket\ConnectException;
|
||||
use Amp\Socket\Connector;
|
||||
use Amp\Socket\ResourceSocket;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
|
||||
use function Amp\Socket\Internal\parseUri;
|
||||
|
||||
class DoHConnector implements Connector
|
||||
{
|
||||
/**
|
||||
* Datacenter instance
|
||||
*
|
||||
* @property DataCenter $dataCenter
|
||||
*/
|
||||
private $dataCenter;
|
||||
/**
|
||||
* Connection context
|
||||
*
|
||||
* @var ConnectionContext
|
||||
*/
|
||||
private $ctx;
|
||||
public function __construct(DataCenter $dataCenter, ConnectionContext $ctx)
|
||||
{
|
||||
$this->dataCenter = $dataCenter;
|
||||
$this->ctx = $ctx;
|
||||
}
|
||||
|
||||
public function connect(string $uri, ?ConnectContext $socketContext = null, ?CancellationToken $token = null): Promise
|
||||
{
|
||||
return Tools::call(function () use ($uri, $socketContext, $token) {
|
||||
$socketContext = $socketContext ?? new ConnectContext;
|
||||
$token = $token ?? new NullCancellationToken;
|
||||
|
||||
$attempt = 0;
|
||||
$uris = [];
|
||||
$failures = [];
|
||||
[$scheme, $host, $port] = parseUri($uri);
|
||||
if ($host[0] === '[') {
|
||||
$host = \substr($host, 1, -1);
|
||||
}
|
||||
if ($port === 0 || @\inet_pton($host)) {
|
||||
// Host is already an IP address or file path.
|
||||
$uris = [$uri];
|
||||
} else {
|
||||
// Host is not an IP address, so resolve the domain name.
|
||||
// When we're connecting to a host, we may need to resolve the domain name, first.
|
||||
// The resolution is usually done using DNS over HTTPS.
|
||||
//
|
||||
// The DNS over HTTPS resolver needs to resolve the domain name of the DOH server:
|
||||
// this is handled internally by the DNS over HTTPS client,
|
||||
// by redirecting the resolution request to the plain DNS client.
|
||||
//
|
||||
// However, if the DoH connection is proxied with a proxy that has a domain name itself,
|
||||
// we cannot resolve it with the DoH resolver, since this will cause an infinite loop
|
||||
//
|
||||
// resolve host.com => (DoH resolver) => resolve dohserver.com => (simple resolver) => OK
|
||||
//
|
||||
// |> resolve dohserver.com => (simple resolver) => OK
|
||||
// resolve host.com => (DoH resolver) =|
|
||||
// |> resolve proxy.com => (non-proxied resolver) => OK
|
||||
//
|
||||
//
|
||||
// This means that we must detect if the domain name we're trying to resolve is a proxy domain name.
|
||||
//
|
||||
// Here, we simply check if the connection URI has changed since we first set it:
|
||||
// this would indicate that a proxy class has changed the connection URI to the proxy URI.
|
||||
//
|
||||
if ($this->ctx->isDns()) {
|
||||
$records = yield $this->dataCenter->getNonProxiedDNSClient()->resolve($host, $socketContext->getDnsTypeRestriction());
|
||||
} else {
|
||||
$records = yield $this->dataCenter->getDNSClient()->resolve($host, $socketContext->getDnsTypeRestriction());
|
||||
}
|
||||
\usort($records, function (Record $a, Record $b) {
|
||||
return $a->getType() - $b->getType();
|
||||
});
|
||||
if ($this->ctx->getIpv6()) {
|
||||
$records = \array_reverse($records);
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
/** @var Record $record */
|
||||
if ($record->getType() === Record::AAAA) {
|
||||
$uris[] = \sprintf("%s://[%s]:%d", $scheme, $record->getValue(), $port);
|
||||
} else {
|
||||
$uris[] = \sprintf("%s://%s:%d", $scheme, $record->getValue(), $port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$flags = \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT;
|
||||
$timeout = $socketContext->getConnectTimeout();
|
||||
foreach ($uris as $builtUri) {
|
||||
try {
|
||||
$streamContext = \stream_context_create($socketContext->withoutTlsContext()->toStreamContextArray());
|
||||
if (!$socket = @\stream_socket_client($builtUri, $errno, $errstr, null, $flags, $streamContext)) {
|
||||
throw new ConnectException(\sprintf(
|
||||
'Connection to %s failed: [Error #%d] %s%s',
|
||||
$uri,
|
||||
$errno,
|
||||
$errstr,
|
||||
$failures ? '; previous attempts: ' . \implode($failures) : ''
|
||||
), $errno);
|
||||
}
|
||||
\stream_set_blocking($socket, false);
|
||||
$deferred = new Deferred;
|
||||
$watcher = Loop::onWritable($socket, [$deferred, 'resolve']);
|
||||
$id = $token->subscribe([$deferred, 'fail']);
|
||||
try {
|
||||
yield Promise\timeout($deferred->promise(), $timeout);
|
||||
} catch (TimeoutException $e) {
|
||||
throw new ConnectException(\sprintf(
|
||||
'Connecting to %s failed: timeout exceeded (%d ms)%s',
|
||||
$uri,
|
||||
$timeout,
|
||||
$failures ? '; previous attempts: ' . \implode($failures) : ''
|
||||
), 110); // See ETIMEDOUT in http://www.virtsync.com/c-error-codes-include-errno
|
||||
} finally {
|
||||
Loop::cancel($watcher);
|
||||
$token->unsubscribe($id);
|
||||
}
|
||||
// The following hack looks like the only way to detect connection refused errors with PHP's stream sockets.
|
||||
if (\stream_socket_get_name($socket, true) === false) {
|
||||
\fclose($socket);
|
||||
throw new ConnectException(\sprintf(
|
||||
'Connection to %s refused%s',
|
||||
$uri,
|
||||
$failures ? '; previous attempts: ' . \implode($failures) : ''
|
||||
), 111); // See ECONNREFUSED in http://www.virtsync.com/c-error-codes-include-errno
|
||||
}
|
||||
} catch (ConnectException $e) {
|
||||
// Includes only error codes used in this file, as error codes on other OS families might be different.
|
||||
// In fact, this might show a confusing error message on OS families that return 110 or 111 by itself.
|
||||
$knownReasons = [
|
||||
110 => 'connection timeout',
|
||||
111 => 'connection refused',
|
||||
];
|
||||
$code = $e->getCode();
|
||||
$reason = $knownReasons[$code] ?? ('Error #' . $code);
|
||||
if (++$attempt === $socketContext->getMaxAttempts()) {
|
||||
break;
|
||||
}
|
||||
$failures[] = "{$uri} ({$reason})";
|
||||
continue; // Could not connect to host, try next host in the list.
|
||||
}
|
||||
return ResourceSocket::fromClientSocket($socket, $socketContext->getTlsContext());
|
||||
}
|
||||
// This is reached if either all URIs failed or the maximum number of attempts is reached.
|
||||
/** @noinspection PhpUndefinedVariableInspection */
|
||||
throw $e;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
0
src/danog/MadelineProto/PredefinedConnector.php
Normal file
0
src/danog/MadelineProto/PredefinedConnector.php
Normal file
@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxying AMPHP connector.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
*
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\Socket\Connector;
|
||||
|
||||
class ProxyConnector implements Connector
|
||||
{
|
||||
private $dataCenter;
|
||||
private $fromDns = false;
|
||||
public function __construct(DataCenter $dataCenter, bool $fromDns = false)
|
||||
{
|
||||
$this->dataCenter = $dataCenter;
|
||||
$this->fromDns = false;
|
||||
}
|
||||
|
||||
public function connect(string $uri, ?ConnectContext $ctx = null, ?CancellationToken $token = null): Promise
|
||||
{
|
||||
return Tools::call(function () use ($uri, $ctx, $token) {
|
||||
$ctx = $ctx ?? new ConnectContext;
|
||||
$token = $token ?? new NullCancellationToken;
|
||||
|
||||
$ctxs = $this->datacenter->generateContexts(0, $uri, $ctx);
|
||||
if (empty($ctxs)) {
|
||||
throw new Exception("No contexts for raw connection to URI $uri");
|
||||
}
|
||||
foreach ($ctxs as $ctx) {
|
||||
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
try {
|
||||
$ctx->setIsDns($this->fromDns);
|
||||
$ctx->setCancellationToken($token);
|
||||
$result = yield $ctx->getStream();
|
||||
$this->API->logger->logger('OK!', \danog\MadelineProto\Logger::WARNING);
|
||||
|
||||
return $result->getSocket();
|
||||
} catch (\Throwable $e) {
|
||||
if (\defined('MADELINEPROTO_TEST') && MADELINEPROTO_TEST === 'pony') {
|
||||
throw $e;
|
||||
}
|
||||
$this->API->logger->logger('Connection failed: '.$e, \danog\MadelineProto\Logger::ERROR);
|
||||
if ($e instanceof MultiReasonException) {
|
||||
foreach ($e->getReasons() as $reason) {
|
||||
$this->API->logger->logger('Multireason: '.$reason, \danog\MadelineProto\Logger::ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new \danog\MadelineProto\Exception("Could not connect to URI $uri");
|
||||
|
||||
});
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ namespace danog\MadelineProto\Stream;
|
||||
|
||||
use Amp\CancellationToken;
|
||||
use Amp\Socket\ConnectContext;
|
||||
use Amp\Uri\Uri;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\ObfuscatedStream;
|
||||
use danog\MadelineProto\Stream\Transport\DefaultStream;
|
||||
|
@ -36,7 +36,7 @@ use function Amp\Socket\connector;
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class DefaultStream extends Socket implements
|
||||
class DefaultStream implements
|
||||
RawStreamInterface,
|
||||
ProxyStreamInterface
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ class WsStream implements RawStreamInterface
|
||||
/**
|
||||
* Websocket stream.
|
||||
*
|
||||
* @var Rfc6455Connection
|
||||
* @var Connection
|
||||
*/
|
||||
private $stream;
|
||||
/**
|
||||
|
@ -20,3 +20,7 @@ if (!\function_exists('error_clear_last')) {
|
||||
@\trigger_error("");
|
||||
}
|
||||
}
|
||||
|
||||
if (!\defined('MADELINEPROTO_TEST')) {
|
||||
\define('MADELINEPROTO_TEST', 'NOT PONY');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user