+
This commit is contained in:
452
vendor/react/dns/CHANGELOG.md
vendored
Normal file
452
vendor/react/dns/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
# Changelog
|
||||
|
||||
## 1.13.0 (2024-06-13)
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
|
||||
(#224 by @WyriHaximus)
|
||||
|
||||
## 1.12.0 (2023-11-29)
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#217 by @sergiy-petrov)
|
||||
|
||||
* Update test environment and avoid unhandled promise rejections.
|
||||
(#215, #216 and #218 by @clue)
|
||||
|
||||
## 1.11.0 (2023-06-02)
|
||||
|
||||
* Feature: Include timeout logic to avoid dependency on reactphp/promise-timer.
|
||||
(#213 by @clue)
|
||||
|
||||
* Improve test suite and project setup and report failed assertions.
|
||||
(#210 by @clue, #212 by @WyriHaximus and #209 and #211 by @SimonFrings)
|
||||
|
||||
## 1.10.0 (2022-09-08)
|
||||
|
||||
* Feature: Full support for PHP 8.2 release.
|
||||
(#201 by @clue and #207 by @WyriHaximus)
|
||||
|
||||
* Feature: Optimize forward compatibility with Promise v3, avoid hitting autoloader.
|
||||
(#202 by @clue)
|
||||
|
||||
* Feature / Fix: Improve error reporting when custom error handler is used.
|
||||
(#197 by @clue)
|
||||
|
||||
* Fix: Fix invalid references in exception stack trace.
|
||||
(#191 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#195 by @SimonFrings and #203 by @nhedger)
|
||||
|
||||
* Improve test suite, update to use default loop and new reactphp/async package.
|
||||
(#204, #205 and #206 by @clue and #196 by @SimonFrings)
|
||||
|
||||
## 1.9.0 (2021-12-20)
|
||||
|
||||
* Feature: Full support for PHP 8.1 release and prepare PHP 8.2 compatibility
|
||||
by refactoring `Parser` to avoid assigning dynamic properties.
|
||||
(#188 and #186 by @clue and #184 by @SimonFrings)
|
||||
|
||||
* Feature: Avoid dependency on `ext-filter`.
|
||||
(#185 by @clue)
|
||||
|
||||
* Feature / Fix: Skip invalid nameserver entries from `resolv.conf` and ignore IPv6 zone IDs.
|
||||
(#187 by @clue)
|
||||
|
||||
* Feature / Fix: Reduce socket read chunk size for queries over TCP/IP.
|
||||
(#189 by @clue)
|
||||
|
||||
## 1.8.0 (2021-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
|
||||
|
||||
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
|
||||
(#182 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$resolver = $factory->create($config, $loop);
|
||||
|
||||
// new (using default loop)
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$resolver = $factory->create($config);
|
||||
```
|
||||
|
||||
## 1.7.0 (2021-06-25)
|
||||
|
||||
* Feature: Update DNS `Factory` to accept complete `Config` object.
|
||||
Add new `FallbackExecutor` and use fallback DNS servers when `Config` lists multiple servers.
|
||||
(#179 and #180 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
$resolver = $factory->create($server, $loop);
|
||||
|
||||
// new
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8';
|
||||
}
|
||||
$resolver = $factory->create($config, $loop);
|
||||
```
|
||||
|
||||
## 1.6.0 (2021-06-21)
|
||||
|
||||
* Feature: Add support for legacy `SPF` record type.
|
||||
(#178 by @akondas and @clue)
|
||||
|
||||
* Fix: Fix integer overflow for TCP/IP chunk size on 32 bit platforms.
|
||||
(#177 by @clue)
|
||||
|
||||
## 1.5.0 (2021-03-05)
|
||||
|
||||
* Feature: Improve error reporting when query fails, include domain and query type and DNS server address where applicable.
|
||||
(#174 by @clue)
|
||||
|
||||
* Feature: Improve error handling when sending data to DNS server fails (macOS).
|
||||
(#171 and #172 by @clue)
|
||||
|
||||
* Fix: Improve DNS response parser to limit recursion for compressed labels.
|
||||
(#169 by @clue)
|
||||
|
||||
* Improve test suite, use GitHub actions for continuous integration (CI).
|
||||
(#170 by @SimonFrings)
|
||||
|
||||
## 1.4.0 (2020-09-18)
|
||||
|
||||
* Feature: Support upcoming PHP 8.
|
||||
(#168 by @clue)
|
||||
|
||||
* Improve test suite and update to PHPUnit 9.3.
|
||||
(#164 by @clue, #165 and #166 by @SimonFrings and #167 by @WyriHaximus)
|
||||
|
||||
## 1.3.0 (2020-07-10)
|
||||
|
||||
* Feature: Forward compatibility with react/promise v3.
|
||||
(#153 by @WyriHaximus)
|
||||
|
||||
* Feature: Support parsing `OPT` records (EDNS0).
|
||||
(#157 by @clue)
|
||||
|
||||
* Fix: Avoid PHP warnings due to lack of args in exception trace on PHP 7.4.
|
||||
(#160 by @clue)
|
||||
|
||||
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
|
||||
Run tests on PHPUnit 9 and PHP 7.4 and clean up test suite.
|
||||
(#154 by @reedy, #156 by @clue and #163 by @SimonFrings)
|
||||
|
||||
## 1.2.0 (2019-08-15)
|
||||
|
||||
* Feature: Add `TcpTransportExecutor` to send DNS queries over TCP/IP connection,
|
||||
add `SelectiveTransportExecutor` to retry with TCP if UDP is truncated and
|
||||
automatically select transport protocol when no explicit `udp://` or `tcp://` scheme is given in `Factory`.
|
||||
(#145, #146, #147 and #148 by @clue)
|
||||
|
||||
* Feature: Support escaping literal dots and special characters in domain names.
|
||||
(#144 by @clue)
|
||||
|
||||
## 1.1.0 (2019-07-18)
|
||||
|
||||
* Feature: Support parsing `CAA` and `SSHFP` records.
|
||||
(#141 and #142 by @clue)
|
||||
|
||||
* Feature: Add `ResolverInterface` as common interface for `Resolver` class.
|
||||
(#139 by @clue)
|
||||
|
||||
* Fix: Add missing private property definitions and
|
||||
remove unneeded dependency on `react/stream`.
|
||||
(#140 and #143 by @clue)
|
||||
|
||||
## 1.0.0 (2019-07-11)
|
||||
|
||||
* First stable LTS release, now following [SemVer](https://semver.org/).
|
||||
We'd like to emphasize that this component is production ready and battle-tested.
|
||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
||||
so you have a rock-solid foundation to build on top of.
|
||||
|
||||
This update involves a number of BC breaks due to dropped support for
|
||||
deprecated functionality and some internal API cleanup. We've tried hard to
|
||||
avoid BC breaks where possible and minimize impact otherwise. We expect that
|
||||
most consumers of this package will actually not be affected by any BC
|
||||
breaks, see below for more details:
|
||||
|
||||
* BC break: Delete all deprecated APIs, use `Query` objects for `Message` questions
|
||||
instead of nested arrays and increase code coverage to 100%.
|
||||
(#130 by @clue)
|
||||
|
||||
* BC break: Move `$nameserver` from `ExecutorInterface` to `UdpTransportExecutor`,
|
||||
remove advanced/internal `UdpTransportExecutor` args for `Parser`/`BinaryDumper` and
|
||||
add API documentation for `ExecutorInterface`.
|
||||
(#135, #137 and #138 by @clue)
|
||||
|
||||
* BC break: Replace `HeaderBag` attributes with simple `Message` properties.
|
||||
(#132 by @clue)
|
||||
|
||||
* BC break: Mark all `Record` attributes as required, add documentation vs `Query`.
|
||||
(#136 by @clue)
|
||||
|
||||
* BC break: Mark all classes as final to discourage inheritance
|
||||
(#134 by @WyriHaximus)
|
||||
|
||||
## 0.4.19 (2019-07-10)
|
||||
|
||||
* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6.
|
||||
(#133 by @clue)
|
||||
|
||||
## 0.4.18 (2019-09-07)
|
||||
|
||||
* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`,
|
||||
respect TTL from response records when caching and do not cache truncated responses.
|
||||
(#129 by @clue)
|
||||
|
||||
* Feature: Limit cache size to 256 last responses by default.
|
||||
(#127 by @clue)
|
||||
|
||||
* Feature: Cooperatively resolve hosts to avoid running same query concurrently.
|
||||
(#125 by @clue)
|
||||
|
||||
## 0.4.17 (2019-04-01)
|
||||
|
||||
* Feature: Support parsing `authority` and `additional` records from DNS response.
|
||||
(#123 by @clue)
|
||||
|
||||
* Feature: Support dumping records as part of outgoing binary DNS message.
|
||||
(#124 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0
|
||||
(#121 by @clue)
|
||||
|
||||
* Improve test suite to add forward compatibility with PHPUnit 7,
|
||||
test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM.
|
||||
(#122 by @clue)
|
||||
|
||||
## 0.4.16 (2018-11-11)
|
||||
|
||||
* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references.
|
||||
(#118 by @clue)
|
||||
|
||||
* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages,
|
||||
malformed record data or malformed compressed domain name labels.
|
||||
(#115 and #117 by @clue)
|
||||
|
||||
* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset.
|
||||
(#116 by @clue)
|
||||
|
||||
* Fix: Fix caching advanced MX/SRV/TXT/SOA structures.
|
||||
(#112 by @clue)
|
||||
|
||||
## 0.4.15 (2018-07-02)
|
||||
|
||||
* Feature: Add `resolveAll()` method to support custom query types in `Resolver`.
|
||||
(#110 by @clue and @WyriHaximus)
|
||||
|
||||
```php
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
||||
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records.
|
||||
(#104, #105, #106, #107 and #108 by @clue)
|
||||
|
||||
* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data.
|
||||
(#104 by @clue)
|
||||
|
||||
* Feature: Improve error messages for failed queries and improve documentation.
|
||||
(#109 by @clue)
|
||||
|
||||
* Feature: Add reverse DNS lookup example.
|
||||
(#111 by @clue)
|
||||
|
||||
## 0.4.14 (2018-06-26)
|
||||
|
||||
* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages
|
||||
to avoid cache poisoning attacks and deprecate legacy `Executor`.
|
||||
(#101 and #103 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with Cache 0.5
|
||||
(#102 by @clue)
|
||||
|
||||
* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API.
|
||||
(#99 by @clue)
|
||||
|
||||
## 0.4.13 (2018-02-27)
|
||||
|
||||
* Add `Config::loadSystemConfigBlocking()` to load default system config
|
||||
and support parsing DNS config on all supported platforms
|
||||
(`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows)
|
||||
(#92, #93, #94 and #95 by @clue)
|
||||
|
||||
```php
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
```
|
||||
|
||||
* Remove unneeded cyclic dependency on react/socket
|
||||
(#96 by @clue)
|
||||
|
||||
## 0.4.12 (2018-01-14)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6,
|
||||
test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases,
|
||||
add test group to skip integration tests relying on internet connection
|
||||
and add minor documentation improvements.
|
||||
(#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor)
|
||||
|
||||
## 0.4.11 (2017-08-25)
|
||||
|
||||
* Feature: Support resolving from default hosts file
|
||||
(#75, #76 and #77 by @clue)
|
||||
|
||||
This means that resolving hosts such as `localhost` will now work as
|
||||
expected across all platforms with no changes required:
|
||||
|
||||
```php
|
||||
$resolver->resolve('localhost')->then(function ($ip) {
|
||||
echo 'IP: ' . $ip;
|
||||
});
|
||||
```
|
||||
|
||||
The new `HostsExecutor` exists for advanced usage and is otherwise used
|
||||
internally for this feature.
|
||||
|
||||
## 0.4.10 (2017-08-10)
|
||||
|
||||
* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and
|
||||
lock minimum dependencies and work around circular dependency for tests
|
||||
(#70 and #71 by @clue)
|
||||
|
||||
* Fix: Work around DNS timeout issues for Windows users
|
||||
(#74 by @clue)
|
||||
|
||||
* Documentation and examples for advanced usage
|
||||
(#66 by @WyriHaximus)
|
||||
|
||||
* Remove broken TCP code, do not retry with invalid TCP query
|
||||
(#73 by @clue)
|
||||
|
||||
* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and
|
||||
lock Travis distro so new defaults will not break the build and
|
||||
fix failing tests for PHP 7.1
|
||||
(#68 by @WyriHaximus and #69 and #72 by @clue)
|
||||
|
||||
## 0.4.9 (2017-05-01)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
|
||||
(#61 by @clue)
|
||||
|
||||
## 0.4.8 (2017-04-16)
|
||||
|
||||
* Feature: Add support for the AAAA record type to the protocol parser
|
||||
(#58 by @othillo)
|
||||
|
||||
* Feature: Add support for the PTR record type to the protocol parser
|
||||
(#59 by @othillo)
|
||||
|
||||
## 0.4.7 (2017-03-31)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component
|
||||
(#57 by @clue)
|
||||
|
||||
## 0.4.6 (2017-03-11)
|
||||
|
||||
* Fix: Fix DNS timeout issues for Windows users and add forward compatibility
|
||||
with Stream v0.5 and upcoming v0.6
|
||||
(#53 by @clue)
|
||||
|
||||
* Improve test suite by adding PHPUnit to `require-dev`
|
||||
(#54 by @clue)
|
||||
|
||||
## 0.4.5 (2017-03-02)
|
||||
|
||||
* Fix: Ensure we ignore the case of the answer
|
||||
(#51 by @WyriHaximus)
|
||||
|
||||
* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal
|
||||
code re-use for upcoming versions.
|
||||
(#48 and #49 by @clue)
|
||||
|
||||
## 0.4.4 (2017-02-13)
|
||||
|
||||
* Fix: Fix handling connection and stream errors
|
||||
(#45 by @clue)
|
||||
|
||||
* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component
|
||||
(#46 and #47 by @clue)
|
||||
|
||||
## 0.4.3 (2016-07-31)
|
||||
|
||||
* Feature: Allow for cache adapter injection (#38 by @WyriHaximus)
|
||||
|
||||
```php
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
|
||||
$cache = new MyCustomCacheInstance();
|
||||
$resolver = $factory->createCached('8.8.8.8', $loop, $cache);
|
||||
```
|
||||
|
||||
* Feature: Support Promise cancellation (#35 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $resolver->resolve('reactphp.org');
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
## 0.4.2 (2016-02-24)
|
||||
|
||||
* Repository maintenance, split off from main repo, improve test suite and documentation
|
||||
* First class support for PHP7 and HHVM (#34 by @clue)
|
||||
* Adjust compatibility to 5.3 (#30 by @clue)
|
||||
|
||||
## 0.4.1 (2014-04-13)
|
||||
|
||||
* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus)
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
||||
* BC break: Update to React/Promise 2.0
|
||||
* Bug fix: Properly resolve CNAME aliases
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
* Bump React dependencies to v0.4
|
||||
|
||||
## 0.3.2 (2013-05-10)
|
||||
|
||||
* Feature: Support default port for IPv6 addresses (@clue)
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* Bump React dependencies to v0.3
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Feature: New cache component, used by DNS
|
||||
|
||||
## 0.2.5 (2012-11-26)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.4 (2012-11-18)
|
||||
|
||||
* Feature: Change to promise-based API (@jsor)
|
||||
|
||||
## 0.2.3 (2012-11-14)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.2 (2012-10-28)
|
||||
|
||||
* Feature: DNS executor timeout handling (@arnaud-lb)
|
||||
* Feature: DNS retry executor (@arnaud-lb)
|
||||
|
||||
## 0.2.1 (2012-10-14)
|
||||
|
||||
* Minor adjustments to DNS parser
|
||||
|
||||
## 0.2.0 (2012-09-10)
|
||||
|
||||
* Feature: DNS resolver
|
||||
21
vendor/react/dns/LICENSE
vendored
Normal file
21
vendor/react/dns/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
453
vendor/react/dns/README.md
vendored
Normal file
453
vendor/react/dns/README.md
vendored
Normal file
@@ -0,0 +1,453 @@
|
||||
# DNS
|
||||
|
||||
[](https://github.com/reactphp/dns/actions)
|
||||
[](https://packagist.org/packages/react/dns)
|
||||
|
||||
Async DNS resolver for [ReactPHP](https://reactphp.org/).
|
||||
|
||||
The main point of the DNS component is to provide async DNS resolution.
|
||||
However, it is really a toolkit for working with DNS messages, and could
|
||||
easily be used to create a DNS server.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Basic usage](#basic-usage)
|
||||
* [Caching](#caching)
|
||||
* [Custom cache adapter](#custom-cache-adapter)
|
||||
* [ResolverInterface](#resolverinterface)
|
||||
* [resolve()](#resolve)
|
||||
* [resolveAll()](#resolveall)
|
||||
* [Advanced usage](#advanced-usage)
|
||||
* [UdpTransportExecutor](#udptransportexecutor)
|
||||
* [TcpTransportExecutor](#tcptransportexecutor)
|
||||
* [SelectiveTransportExecutor](#selectivetransportexecutor)
|
||||
* [HostsFileExecutor](#hostsfileexecutor)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
* [References](#references)
|
||||
|
||||
## Basic usage
|
||||
|
||||
The most basic usage is to just create a resolver through the resolver
|
||||
factory. All you need to give it is a nameserver, then you can start resolving
|
||||
names, baby!
|
||||
|
||||
```php
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8';
|
||||
}
|
||||
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->create($config);
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
```
|
||||
|
||||
See also the [first example](examples).
|
||||
|
||||
The `Config` class can be used to load the system default config. This is an
|
||||
operation that may access the filesystem and block. Ideally, this method should
|
||||
thus be executed only once before the loop starts and not repeatedly while it is
|
||||
running.
|
||||
Note that this class may return an *empty* configuration if the system config
|
||||
can not be loaded. As such, you'll likely want to apply a default nameserver
|
||||
as above if none can be found.
|
||||
|
||||
> Note that the factory loads the hosts file from the filesystem once when
|
||||
creating the resolver instance.
|
||||
Ideally, this method should thus be executed only once before the loop starts
|
||||
and not repeatedly while it is running.
|
||||
|
||||
But there's more.
|
||||
|
||||
## Caching
|
||||
|
||||
You can cache results by configuring the resolver to use a `CachedExecutor`:
|
||||
|
||||
```php
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8';
|
||||
}
|
||||
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->createCached($config);
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
|
||||
...
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
```
|
||||
|
||||
If the first call returns before the second, only one query will be executed.
|
||||
The second result will be served from an in memory cache.
|
||||
This is particularly useful for long running scripts where the same hostnames
|
||||
have to be looked up multiple times.
|
||||
|
||||
See also the [third example](examples).
|
||||
|
||||
### Custom cache adapter
|
||||
|
||||
By default, the above will use an in memory cache.
|
||||
|
||||
You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead:
|
||||
|
||||
```php
|
||||
$cache = new React\Cache\ArrayCache();
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->createCached('8.8.8.8', null, $cache);
|
||||
```
|
||||
|
||||
See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
|
||||
|
||||
## ResolverInterface
|
||||
|
||||
<a id="resolver"><!-- legacy reference --></a>
|
||||
|
||||
### resolve()
|
||||
|
||||
The `resolve(string $domain): PromiseInterface<string>` method can be used to
|
||||
resolve the given $domain name to a single IPv4 address (type `A` query).
|
||||
|
||||
```php
|
||||
$resolver->resolve('reactphp.org')->then(function ($ip) {
|
||||
echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
This is one of the main methods in this package. It sends a DNS query
|
||||
for the given $domain name to your DNS server and returns a single IP
|
||||
address on success.
|
||||
|
||||
If the DNS server sends a DNS response message that contains more than
|
||||
one IP address for this query, it will randomly pick one of the IP
|
||||
addresses from the response. If you want the full list of IP addresses
|
||||
or want to send a different type of query, you should use the
|
||||
[`resolveAll()`](#resolveall) method instead.
|
||||
|
||||
If the DNS server sends a DNS response message that indicates an error
|
||||
code, this method will reject with a `RecordNotFoundException`. Its
|
||||
message and code can be used to check for the response code.
|
||||
|
||||
If the DNS communication fails and the server does not respond with a
|
||||
valid response message, this message will reject with an `Exception`.
|
||||
|
||||
Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
|
||||
```php
|
||||
$promise = $resolver->resolve('reactphp.org');
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
### resolveAll()
|
||||
|
||||
The `resolveAll(string $host, int $type): PromiseInterface<array>` method can be used to
|
||||
resolve all record values for the given $domain name and query $type.
|
||||
|
||||
```php
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
|
||||
echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
});
|
||||
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
||||
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
This is one of the main methods in this package. It sends a DNS query
|
||||
for the given $domain name to your DNS server and returns a list with all
|
||||
record values on success.
|
||||
|
||||
If the DNS server sends a DNS response message that contains one or more
|
||||
records for this query, it will return a list with all record values
|
||||
from the response. You can use the `Message::TYPE_*` constants to control
|
||||
which type of query will be sent. Note that this method always returns a
|
||||
list of record values, but each record value type depends on the query
|
||||
type. For example, it returns the IPv4 addresses for type `A` queries,
|
||||
the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
|
||||
`CNAME` and `PTR` queries and structured data for other queries. See also
|
||||
the `Record` documentation for more details.
|
||||
|
||||
If the DNS server sends a DNS response message that indicates an error
|
||||
code, this method will reject with a `RecordNotFoundException`. Its
|
||||
message and code can be used to check for the response code.
|
||||
|
||||
If the DNS communication fails and the server does not respond with a
|
||||
valid response message, this message will reject with an `Exception`.
|
||||
|
||||
Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
|
||||
```php
|
||||
$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### UdpTransportExecutor
|
||||
|
||||
The `UdpTransportExecutor` can be used to
|
||||
send DNS queries over a UDP transport.
|
||||
|
||||
This is the main class that sends a DNS query to your DNS server and is used
|
||||
internally by the `Resolver` for the actual message transport.
|
||||
|
||||
For more advanced usages one can utilize this class directly.
|
||||
The following example looks up the `IPv6` address for `igor.io`.
|
||||
|
||||
```php
|
||||
$executor = new UdpTransportExecutor('8.8.8.8:53');
|
||||
|
||||
$executor->query(
|
||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
```
|
||||
|
||||
See also the [fourth example](examples).
|
||||
|
||||
Note that this executor does not implement a timeout, so you will very likely
|
||||
want to use this in combination with a `TimeoutExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new TimeoutExecutor(
|
||||
new UdpTransportExecutor($nameserver),
|
||||
3.0
|
||||
);
|
||||
```
|
||||
|
||||
Also note that this executor uses an unreliable UDP transport and that it
|
||||
does not implement any retry logic, so you will likely want to use this in
|
||||
combination with a `RetryExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new RetryExecutor(
|
||||
new TimeoutExecutor(
|
||||
new UdpTransportExecutor($nameserver),
|
||||
3.0
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
Note that this executor is entirely async and as such allows you to execute
|
||||
any number of queries concurrently. You should probably limit the number of
|
||||
concurrent queries in your application or you're very likely going to face
|
||||
rate limitations and bans on the resolver end. For many common applications,
|
||||
you may want to avoid sending the same query multiple times when the first
|
||||
one is still pending, so you will likely want to use this in combination with
|
||||
a `CoopExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new CoopExecutor(
|
||||
new RetryExecutor(
|
||||
new TimeoutExecutor(
|
||||
new UdpTransportExecutor($nameserver),
|
||||
3.0
|
||||
)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
> Internally, this class uses PHP's UDP sockets and does not take advantage
|
||||
of [react/datagram](https://github.com/reactphp/datagram) purely for
|
||||
organizational reasons to avoid a cyclic dependency between the two
|
||||
packages. Higher-level components should take advantage of the Datagram
|
||||
component instead of reimplementing this socket logic from scratch.
|
||||
|
||||
### TcpTransportExecutor
|
||||
|
||||
The `TcpTransportExecutor` class can be used to
|
||||
send DNS queries over a TCP/IP stream transport.
|
||||
|
||||
This is one of the main classes that send a DNS query to your DNS server.
|
||||
|
||||
For more advanced usages one can utilize this class directly.
|
||||
The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
|
||||
```php
|
||||
$executor = new TcpTransportExecutor('8.8.8.8:53');
|
||||
|
||||
$executor->query(
|
||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
```
|
||||
|
||||
See also [example #92](examples).
|
||||
|
||||
Note that this executor does not implement a timeout, so you will very likely
|
||||
want to use this in combination with a `TimeoutExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new TimeoutExecutor(
|
||||
new TcpTransportExecutor($nameserver),
|
||||
3.0
|
||||
);
|
||||
```
|
||||
|
||||
Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
|
||||
transport, so you do not necessarily have to implement any retry logic.
|
||||
|
||||
Note that this executor is entirely async and as such allows you to execute
|
||||
queries concurrently. The first query will establish a TCP/IP socket
|
||||
connection to the DNS server which will be kept open for a short period.
|
||||
Additional queries will automatically reuse this existing socket connection
|
||||
to the DNS server, will pipeline multiple requests over this single
|
||||
connection and will keep an idle connection open for a short period. The
|
||||
initial TCP/IP connection overhead may incur a slight delay if you only send
|
||||
occasional queries – when sending a larger number of concurrent queries over
|
||||
an existing connection, it becomes increasingly more efficient and avoids
|
||||
creating many concurrent sockets like the UDP-based executor. You may still
|
||||
want to limit the number of (concurrent) queries in your application or you
|
||||
may be facing rate limitations and bans on the resolver end. For many common
|
||||
applications, you may want to avoid sending the same query multiple times
|
||||
when the first one is still pending, so you will likely want to use this in
|
||||
combination with a `CoopExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new CoopExecutor(
|
||||
new TimeoutExecutor(
|
||||
new TcpTransportExecutor($nameserver),
|
||||
3.0
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
> Internally, this class uses PHP's TCP/IP sockets and does not take advantage
|
||||
of [react/socket](https://github.com/reactphp/socket) purely for
|
||||
organizational reasons to avoid a cyclic dependency between the two
|
||||
packages. Higher-level components should take advantage of the Socket
|
||||
component instead of reimplementing this socket logic from scratch.
|
||||
|
||||
### SelectiveTransportExecutor
|
||||
|
||||
The `SelectiveTransportExecutor` class can be used to
|
||||
Send DNS queries over a UDP or TCP/IP stream transport.
|
||||
|
||||
This class will automatically choose the correct transport protocol to send
|
||||
a DNS query to your DNS server. It will always try to send it over the more
|
||||
efficient UDP transport first. If this query yields a size related issue
|
||||
(truncated messages), it will retry over a streaming TCP/IP transport.
|
||||
|
||||
For more advanced usages one can utilize this class directly.
|
||||
The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
|
||||
```php
|
||||
$executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
|
||||
|
||||
$executor->query(
|
||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
```
|
||||
|
||||
Note that this executor only implements the logic to select the correct
|
||||
transport for the given DNS query. Implementing the correct transport logic,
|
||||
implementing timeouts and any retry logic is left up to the given executors,
|
||||
see also [`UdpTransportExecutor`](#udptransportexecutor) and
|
||||
[`TcpTransportExecutor`](#tcptransportexecutor) for more details.
|
||||
|
||||
Note that this executor is entirely async and as such allows you to execute
|
||||
any number of queries concurrently. You should probably limit the number of
|
||||
concurrent queries in your application or you're very likely going to face
|
||||
rate limitations and bans on the resolver end. For many common applications,
|
||||
you may want to avoid sending the same query multiple times when the first
|
||||
one is still pending, so you will likely want to use this in combination with
|
||||
a `CoopExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new CoopExecutor(
|
||||
new SelectiveTransportExecutor(
|
||||
$datagramExecutor,
|
||||
$streamExecutor
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### HostsFileExecutor
|
||||
|
||||
Note that the above `UdpTransportExecutor` class always performs an actual DNS query.
|
||||
If you also want to take entries from your hosts file into account, you may
|
||||
use this code:
|
||||
|
||||
```php
|
||||
$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();
|
||||
|
||||
$executor = new UdpTransportExecutor('8.8.8.8:53');
|
||||
$executor = new HostsFileExecutor($hosts, $executor);
|
||||
|
||||
$executor->query(
|
||||
new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
|
||||
);
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org/).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This project follows [SemVer](https://semver.org/).
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
composer require react/dns:^1.13
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org/):
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
The test suite also contains a number of functional integration tests that rely
|
||||
on a stable internet connection.
|
||||
If you do not want to run these, they can simply be skipped like this:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit --exclude-group internet
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
|
||||
## References
|
||||
|
||||
* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities
|
||||
* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification
|
||||
49
vendor/react/dns/composer.json
vendored
Normal file
49
vendor/react/dns/composer.json
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "react/dns",
|
||||
"description": "Async DNS resolver for ReactPHP",
|
||||
"keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"homepage": "https://clue.engineering/",
|
||||
"email": "christian@clue.engineering"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"homepage": "https://wyrihaximus.net/",
|
||||
"email": "reactphp@ceesjankiewiet.nl"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"homepage": "https://sorgalla.com/",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"homepage": "https://cboden.dev/",
|
||||
"email": "cboden@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"react/cache": "^1.0 || ^0.6 || ^0.5",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4.3 || ^3 || ^2",
|
||||
"react/promise-timer": "^1.11"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Dns\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Dns\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/BadServerException.php
vendored
Normal file
7
vendor/react/dns/src/BadServerException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns;
|
||||
|
||||
final class BadServerException extends \Exception
|
||||
{
|
||||
}
|
||||
137
vendor/react/dns/src/Config/Config.php
vendored
Normal file
137
vendor/react/dns/src/Config/Config.php
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Config;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class Config
|
||||
{
|
||||
/**
|
||||
* Loads the system DNS configuration
|
||||
*
|
||||
* Note that this method may block while loading its internal files and/or
|
||||
* commands and should thus be used with care! While this should be
|
||||
* relatively fast for most systems, it remains unknown if this may block
|
||||
* under certain circumstances. In particular, this method should only be
|
||||
* executed before the loop starts, not while it is running.
|
||||
*
|
||||
* Note that this method will try to access its files and/or commands and
|
||||
* try to parse its output. Currently, this will only parse valid nameserver
|
||||
* entries from its output and will ignore all other output without
|
||||
* complaining.
|
||||
*
|
||||
* Note that the previous section implies that this may return an empty
|
||||
* `Config` object if no valid nameserver entries can be found.
|
||||
*
|
||||
* @return self
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function loadSystemConfigBlocking()
|
||||
{
|
||||
// Use WMIC output on Windows
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
return self::loadWmicBlocking();
|
||||
}
|
||||
|
||||
// otherwise (try to) load from resolv.conf
|
||||
try {
|
||||
return self::loadResolvConfBlocking();
|
||||
} catch (RuntimeException $ignored) {
|
||||
// return empty config if parsing fails (file not found)
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a resolv.conf file (from the given path or default location)
|
||||
*
|
||||
* Note that this method blocks while loading the given path and should
|
||||
* thus be used with care! While this should be relatively fast for normal
|
||||
* resolv.conf files, this may be an issue if this file is located on a slow
|
||||
* device or contains an excessive number of entries. In particular, this
|
||||
* method should only be executed before the loop starts, not while it is
|
||||
* running.
|
||||
*
|
||||
* Note that this method will throw if the given file can not be loaded,
|
||||
* such as if it is not readable or does not exist. In particular, this file
|
||||
* is not available on Windows.
|
||||
*
|
||||
* Currently, this will only parse valid "nameserver X" lines from the
|
||||
* given file contents. Lines can be commented out with "#" and ";" and
|
||||
* invalid lines will be ignored without complaining. See also
|
||||
* `man resolv.conf` for more details.
|
||||
*
|
||||
* Note that the previous section implies that this may return an empty
|
||||
* `Config` object if no valid "nameserver X" lines can be found. See also
|
||||
* `man resolv.conf` which suggests that the DNS server on the localhost
|
||||
* should be used in this case. This is left up to higher level consumers
|
||||
* of this API.
|
||||
*
|
||||
* @param ?string $path (optional) path to resolv.conf file or null=load default location
|
||||
* @return self
|
||||
* @throws RuntimeException if the path can not be loaded (does not exist)
|
||||
*/
|
||||
public static function loadResolvConfBlocking($path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = '/etc/resolv.conf';
|
||||
}
|
||||
|
||||
$contents = @file_get_contents($path);
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException('Unable to load resolv.conf file "' . $path . '"');
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches);
|
||||
|
||||
$config = new self();
|
||||
foreach ($matches[1] as $ip) {
|
||||
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
|
||||
if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
|
||||
$ip = substr($ip, 0, $pos);
|
||||
}
|
||||
|
||||
if (@inet_pton($ip) !== false) {
|
||||
$config->nameservers[] = $ip;
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the DNS configurations from Windows's WMIC (from the given command or default command)
|
||||
*
|
||||
* Note that this method blocks while loading the given command and should
|
||||
* thus be used with care! While this should be relatively fast for normal
|
||||
* WMIC commands, it remains unknown if this may block under certain
|
||||
* circumstances. In particular, this method should only be executed before
|
||||
* the loop starts, not while it is running.
|
||||
*
|
||||
* Note that this method will only try to execute the given command try to
|
||||
* parse its output, irrespective of whether this command exists. In
|
||||
* particular, this command is only available on Windows. Currently, this
|
||||
* will only parse valid nameserver entries from the command output and will
|
||||
* ignore all other output without complaining.
|
||||
*
|
||||
* Note that the previous section implies that this may return an empty
|
||||
* `Config` object if no valid nameserver entries can be found.
|
||||
*
|
||||
* @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
|
||||
* @return self
|
||||
* @link https://ss64.com/nt/wmic.html
|
||||
*/
|
||||
public static function loadWmicBlocking($command = null)
|
||||
{
|
||||
$contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
|
||||
preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
|
||||
|
||||
$config = new self();
|
||||
$config->nameservers = $matches[1];
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public $nameservers = array();
|
||||
}
|
||||
153
vendor/react/dns/src/Config/HostsFile.php
vendored
Normal file
153
vendor/react/dns/src/Config/HostsFile.php
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Config;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Represents a static hosts file which maps hostnames to IPs
|
||||
*
|
||||
* Hosts files are used on most systems to avoid actually hitting the DNS for
|
||||
* certain common hostnames.
|
||||
*
|
||||
* Most notably, this file usually contains an entry to map "localhost" to the
|
||||
* local IP. Windows is a notable exception here, as Windows does not actually
|
||||
* include "localhost" in this file by default. To compensate for this, this
|
||||
* class may explicitly be wrapped in another HostsFile instance which
|
||||
* hard-codes these entries for Windows (see also Factory).
|
||||
*
|
||||
* This class mostly exists to abstract the parsing/extraction process so this
|
||||
* can be replaced with a faster alternative in the future.
|
||||
*/
|
||||
class HostsFile
|
||||
{
|
||||
/**
|
||||
* Returns the default path for the hosts file on this system
|
||||
*
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function getDefaultPath()
|
||||
{
|
||||
// use static path for all Unix-based systems
|
||||
if (DIRECTORY_SEPARATOR !== '\\') {
|
||||
return '/etc/hosts';
|
||||
}
|
||||
|
||||
// Windows actually stores the path in the registry under
|
||||
// \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath
|
||||
$path = '%SystemRoot%\\system32\drivers\etc\hosts';
|
||||
|
||||
$base = getenv('SystemRoot');
|
||||
if ($base === false) {
|
||||
$base = 'C:\\Windows';
|
||||
}
|
||||
|
||||
return str_replace('%SystemRoot%', $base, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a hosts file (from the given path or default location)
|
||||
*
|
||||
* Note that this method blocks while loading the given path and should
|
||||
* thus be used with care! While this should be relatively fast for normal
|
||||
* hosts file, this may be an issue if this file is located on a slow device
|
||||
* or contains an excessive number of entries. In particular, this method
|
||||
* should only be executed before the loop starts, not while it is running.
|
||||
*
|
||||
* @param ?string $path (optional) path to hosts file or null=load default location
|
||||
* @return self
|
||||
* @throws RuntimeException if the path can not be loaded (does not exist)
|
||||
*/
|
||||
public static function loadFromPathBlocking($path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = self::getDefaultPath();
|
||||
}
|
||||
|
||||
$contents = @file_get_contents($path);
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException('Unable to load hosts file "' . $path . '"');
|
||||
}
|
||||
|
||||
return new self($contents);
|
||||
}
|
||||
|
||||
private $contents;
|
||||
|
||||
/**
|
||||
* Instantiate new hosts file with the given hosts file contents
|
||||
*
|
||||
* @param string $contents
|
||||
*/
|
||||
public function __construct($contents)
|
||||
{
|
||||
// remove all comments from the contents
|
||||
$contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents));
|
||||
|
||||
$this->contents = $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all IPs for the given hostname
|
||||
*
|
||||
* @param string $name
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIpsForHost($name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
|
||||
$ips = array();
|
||||
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
|
||||
$parts = preg_split('/\s+/', $line);
|
||||
$ip = array_shift($parts);
|
||||
if ($parts && array_search($name, $parts) !== false) {
|
||||
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
|
||||
if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
|
||||
$ip = substr($ip, 0, $pos);
|
||||
}
|
||||
|
||||
if (@inet_pton($ip) !== false) {
|
||||
$ips[] = $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all hostnames for the given IPv4 or IPv6 address
|
||||
*
|
||||
* @param string $ip
|
||||
* @return string[]
|
||||
*/
|
||||
public function getHostsForIp($ip)
|
||||
{
|
||||
// check binary representation of IP to avoid string case and short notation
|
||||
$ip = @inet_pton($ip);
|
||||
if ($ip === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$names = array();
|
||||
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
|
||||
$parts = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$addr = (string) array_shift($parts);
|
||||
|
||||
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
|
||||
if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
|
||||
$addr = substr($addr, 0, $pos);
|
||||
}
|
||||
|
||||
if (@inet_pton($addr) === $ip) {
|
||||
foreach ($parts as $part) {
|
||||
$names[] = $part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
}
|
||||
230
vendor/react/dns/src/Model/Message.php
vendored
Normal file
230
vendor/react/dns/src/Model/Message.php
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Model;
|
||||
|
||||
use React\Dns\Query\Query;
|
||||
|
||||
/**
|
||||
* This class represents an outgoing query message or an incoming response message
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc1035#section-4.1.1
|
||||
*/
|
||||
final class Message
|
||||
{
|
||||
const TYPE_A = 1;
|
||||
const TYPE_NS = 2;
|
||||
const TYPE_CNAME = 5;
|
||||
const TYPE_SOA = 6;
|
||||
const TYPE_PTR = 12;
|
||||
const TYPE_MX = 15;
|
||||
const TYPE_TXT = 16;
|
||||
const TYPE_AAAA = 28;
|
||||
const TYPE_SRV = 33;
|
||||
const TYPE_SSHFP = 44;
|
||||
|
||||
/**
|
||||
* pseudo-type for EDNS0
|
||||
*
|
||||
* These are included in the additional section and usually not in answer section.
|
||||
* Defined in [RFC 6891](https://tools.ietf.org/html/rfc6891) (or older
|
||||
* [RFC 2671](https://tools.ietf.org/html/rfc2671)).
|
||||
*
|
||||
* The OPT record uses the "class" field to store the maximum size.
|
||||
*
|
||||
* The OPT record uses the "ttl" field to store additional flags.
|
||||
*/
|
||||
const TYPE_OPT = 41;
|
||||
|
||||
/**
|
||||
* Sender Policy Framework (SPF) had a dedicated SPF type which has been
|
||||
* deprecated in favor of reusing the existing TXT type.
|
||||
*
|
||||
* @deprecated https://datatracker.ietf.org/doc/html/rfc7208#section-3.1
|
||||
* @see self::TYPE_TXT
|
||||
*/
|
||||
const TYPE_SPF = 99;
|
||||
|
||||
const TYPE_ANY = 255;
|
||||
const TYPE_CAA = 257;
|
||||
|
||||
const CLASS_IN = 1;
|
||||
|
||||
const OPCODE_QUERY = 0;
|
||||
const OPCODE_IQUERY = 1; // inverse query
|
||||
const OPCODE_STATUS = 2;
|
||||
|
||||
const RCODE_OK = 0;
|
||||
const RCODE_FORMAT_ERROR = 1;
|
||||
const RCODE_SERVER_FAILURE = 2;
|
||||
const RCODE_NAME_ERROR = 3;
|
||||
const RCODE_NOT_IMPLEMENTED = 4;
|
||||
const RCODE_REFUSED = 5;
|
||||
|
||||
/**
|
||||
* The edns-tcp-keepalive EDNS0 Option
|
||||
*
|
||||
* Option value contains a `?float` with timeout in seconds (in 0.1s steps)
|
||||
* for DNS response or `null` for DNS query.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7828
|
||||
*/
|
||||
const OPT_TCP_KEEPALIVE = 11;
|
||||
|
||||
/**
|
||||
* The EDNS(0) Padding Option
|
||||
*
|
||||
* Option value contains a `string` with binary data (usually variable
|
||||
* number of null bytes)
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7830
|
||||
*/
|
||||
const OPT_PADDING = 12;
|
||||
|
||||
/**
|
||||
* Creates a new request message for the given query
|
||||
*
|
||||
* @param Query $query
|
||||
* @return self
|
||||
*/
|
||||
public static function createRequestForQuery(Query $query)
|
||||
{
|
||||
$request = new Message();
|
||||
$request->id = self::generateId();
|
||||
$request->rd = true;
|
||||
$request->questions[] = $query;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new response message for the given query with the given answer records
|
||||
*
|
||||
* @param Query $query
|
||||
* @param Record[] $answers
|
||||
* @return self
|
||||
*/
|
||||
public static function createResponseWithAnswersForQuery(Query $query, array $answers)
|
||||
{
|
||||
$response = new Message();
|
||||
$response->id = self::generateId();
|
||||
$response->qr = true;
|
||||
$response->rd = true;
|
||||
|
||||
$response->questions[] = $query;
|
||||
|
||||
foreach ($answers as $record) {
|
||||
$response->answers[] = $record;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* generates a random 16 bit message ID
|
||||
*
|
||||
* This uses a CSPRNG so that an outside attacker that is sending spoofed
|
||||
* DNS response messages can not guess the message ID to avoid possible
|
||||
* cache poisoning attacks.
|
||||
*
|
||||
* The `random_int()` function is only available on PHP 7+ or when
|
||||
* https://github.com/paragonie/random_compat is installed. As such, using
|
||||
* the latest supported PHP version is highly recommended. This currently
|
||||
* falls back to a less secure random number generator on older PHP versions
|
||||
* in the hope that this system is properly protected against outside
|
||||
* attackers, for example by using one of the common local DNS proxy stubs.
|
||||
*
|
||||
* @return int
|
||||
* @see self::getId()
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private static function generateId()
|
||||
{
|
||||
if (function_exists('random_int')) {
|
||||
return random_int(0, 0xffff);
|
||||
}
|
||||
return mt_rand(0, 0xffff);
|
||||
}
|
||||
|
||||
/**
|
||||
* The 16 bit message ID
|
||||
*
|
||||
* The response message ID has to match the request message ID. This allows
|
||||
* the receiver to verify this is the correct response message. An outside
|
||||
* attacker may try to inject fake responses by "guessing" the message ID,
|
||||
* so this should use a proper CSPRNG to avoid possible cache poisoning.
|
||||
*
|
||||
* @var int 16 bit message ID
|
||||
* @see self::generateId()
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* @var bool Query/Response flag, query=false or response=true
|
||||
*/
|
||||
public $qr = false;
|
||||
|
||||
/**
|
||||
* @var int specifies the kind of query (4 bit), see self::OPCODE_* constants
|
||||
* @see self::OPCODE_QUERY
|
||||
*/
|
||||
public $opcode = self::OPCODE_QUERY;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var bool Authoritative Answer
|
||||
*/
|
||||
public $aa = false;
|
||||
|
||||
/**
|
||||
* @var bool TrunCation
|
||||
*/
|
||||
public $tc = false;
|
||||
|
||||
/**
|
||||
* @var bool Recursion Desired
|
||||
*/
|
||||
public $rd = false;
|
||||
|
||||
/**
|
||||
* @var bool Recursion Available
|
||||
*/
|
||||
public $ra = false;
|
||||
|
||||
/**
|
||||
* @var int response code (4 bit), see self::RCODE_* constants
|
||||
* @see self::RCODE_OK
|
||||
*/
|
||||
public $rcode = Message::RCODE_OK;
|
||||
|
||||
/**
|
||||
* An array of Query objects
|
||||
*
|
||||
* ```php
|
||||
* $questions = array(
|
||||
* new Query(
|
||||
* 'reactphp.org',
|
||||
* Message::TYPE_A,
|
||||
* Message::CLASS_IN
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @var Query[]
|
||||
*/
|
||||
public $questions = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $answers = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $authority = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $additional = array();
|
||||
}
|
||||
153
vendor/react/dns/src/Model/Record.php
vendored
Normal file
153
vendor/react/dns/src/Model/Record.php
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Model;
|
||||
|
||||
/**
|
||||
* This class represents a single resulting record in a response message
|
||||
*
|
||||
* It uses a structure similar to `\React\Dns\Query\Query`, but does include
|
||||
* fields for resulting TTL and resulting record data (IPs etc.).
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||
* @see \React\Dns\Query\Query
|
||||
*/
|
||||
final class Record
|
||||
{
|
||||
/**
|
||||
* @var string hostname without trailing dot, for example "reactphp.org"
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var int see Message::TYPE_* constants (UINT16)
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Defines the network class, usually `Message::CLASS_IN`.
|
||||
*
|
||||
* For `OPT` records (EDNS0), this defines the maximum message size instead.
|
||||
*
|
||||
* @var int see Message::CLASS_IN constant (UINT16)
|
||||
* @see Message::CLASS_IN
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* Defines the maximum time-to-live (TTL) in seconds
|
||||
*
|
||||
* For `OPT` records (EDNS0), this defines additional flags instead.
|
||||
*
|
||||
* @var int maximum TTL in seconds (UINT32, most significant bit always unset)
|
||||
* @link https://tools.ietf.org/html/rfc2181#section-8
|
||||
* @link https://tools.ietf.org/html/rfc6891#section-6.1.3 for `OPT` records (EDNS0)
|
||||
*/
|
||||
public $ttl;
|
||||
|
||||
/**
|
||||
* The payload data for this record
|
||||
*
|
||||
* The payload data format depends on the record type. As a rule of thumb,
|
||||
* this library will try to express this in a way that can be consumed
|
||||
* easily without having to worry about DNS internals and its binary transport:
|
||||
*
|
||||
* - A:
|
||||
* IPv4 address string, for example "192.168.1.1".
|
||||
*
|
||||
* - AAAA:
|
||||
* IPv6 address string, for example "::1".
|
||||
*
|
||||
* - CNAME / PTR / NS:
|
||||
* The hostname without trailing dot, for example "reactphp.org".
|
||||
*
|
||||
* - TXT:
|
||||
* List of string values, for example `["v=spf1 include:example.com"]`.
|
||||
* This is commonly a list with only a single string value, but this
|
||||
* technically allows multiple strings (0-255 bytes each) in a single
|
||||
* record. This is rarely used and depending on application you may want
|
||||
* to join these together or handle them separately. Each string can
|
||||
* transport any binary data, its character encoding is not defined (often
|
||||
* ASCII/UTF-8 in practice). [RFC 1464](https://tools.ietf.org/html/rfc1464)
|
||||
* suggests using key-value pairs such as `["name=test","version=1"]`, but
|
||||
* interpretation of this is not enforced and left up to consumers of this
|
||||
* library (used for DNS-SD/Zeroconf and others).
|
||||
*
|
||||
* - MX:
|
||||
* Mail server priority (UINT16) and target hostname without trailing dot,
|
||||
* for example `{"priority":10,"target":"mx.example.com"}`.
|
||||
* The payload data uses an associative array with fixed keys "priority"
|
||||
* (also commonly referred to as weight or preference) and "target" (also
|
||||
* referred to as exchange). If a response message contains multiple
|
||||
* records of this type, targets should be sorted by priority (lowest
|
||||
* first) - this is left up to consumers of this library (used for SMTP).
|
||||
*
|
||||
* - SRV:
|
||||
* Service priority (UINT16), service weight (UINT16), service port (UINT16)
|
||||
* and target hostname without trailing dot, for example
|
||||
* `{"priority":10,"weight":50,"port":8080,"target":"example.com"}`.
|
||||
* The payload data uses an associative array with fixed keys "priority",
|
||||
* "weight", "port" and "target" (also referred to as name).
|
||||
* The target may be an empty host name string if the service is decidedly
|
||||
* not available. If a response message contains multiple records of this
|
||||
* type, targets should be sorted by priority (lowest first) and selected
|
||||
* randomly according to their weight - this is left up to consumers of
|
||||
* this library, see also [RFC 2782](https://tools.ietf.org/html/rfc2782)
|
||||
* for more details.
|
||||
*
|
||||
* - SSHFP:
|
||||
* Includes algorithm (UNIT8), fingerprint type (UNIT8) and fingerprint
|
||||
* value as lower case hex string, for example:
|
||||
* `{"algorithm":1,"type":1,"fingerprint":"0123456789abcdef..."}`
|
||||
* See also https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xhtml
|
||||
* for algorithm and fingerprint type assignments.
|
||||
*
|
||||
* - SOA:
|
||||
* Includes master hostname without trailing dot, responsible person email
|
||||
* as hostname without trailing dot and serial, refresh, retry, expire and
|
||||
* minimum times in seconds (UINT32 each), for example:
|
||||
* `{"mname":"ns.example.com","rname":"hostmaster.example.com","serial":
|
||||
* 2018082601,"refresh":3600,"retry":1800,"expire":60000,"minimum":3600}`.
|
||||
*
|
||||
* - CAA:
|
||||
* Includes flag (UNIT8), tag string and value string, for example:
|
||||
* `{"flag":128,"tag":"issue","value":"letsencrypt.org"}`
|
||||
*
|
||||
* - OPT:
|
||||
* Special pseudo-type for EDNS0. Includes an array of additional opt codes
|
||||
* with a value according to the respective OPT code. See `Message::OPT_*`
|
||||
* for list of supported OPT codes. Any other OPT code not currently
|
||||
* supported will be an opaque binary string containing the raw data
|
||||
* as transported in the DNS record. For forwards compatibility, you should
|
||||
* not rely on this format for unknown types. Future versions may add
|
||||
* support for new types and this may then parse the payload data
|
||||
* appropriately - this will not be considered a BC break. See also
|
||||
* [RFC 6891](https://tools.ietf.org/html/rfc6891) for more details.
|
||||
*
|
||||
* - Any other unknown type:
|
||||
* An opaque binary string containing the RDATA as transported in the DNS
|
||||
* record. For forwards compatibility, you should not rely on this format
|
||||
* for unknown types. Future versions may add support for new types and
|
||||
* this may then parse the payload data appropriately - this will not be
|
||||
* considered a BC break. See the format definition of known types above
|
||||
* for more details.
|
||||
*
|
||||
* @var string|string[]|array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param int $type
|
||||
* @param int $class
|
||||
* @param int $ttl
|
||||
* @param string|string[]|array $data
|
||||
*/
|
||||
public function __construct($name, $type, $class, $ttl, $data)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->class = $class;
|
||||
$this->ttl = $ttl;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
199
vendor/react/dns/src/Protocol/BinaryDumper.php
vendored
Normal file
199
vendor/react/dns/src/Protocol/BinaryDumper.php
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Protocol;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Dns\Query\Query;
|
||||
|
||||
final class BinaryDumper
|
||||
{
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return string
|
||||
*/
|
||||
public function toBinary(Message $message)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
$data .= $this->headerToBinary($message);
|
||||
$data .= $this->questionToBinary($message->questions);
|
||||
$data .= $this->recordsToBinary($message->answers);
|
||||
$data .= $this->recordsToBinary($message->authority);
|
||||
$data .= $this->recordsToBinary($message->additional);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return string
|
||||
*/
|
||||
private function headerToBinary(Message $message)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
$data .= pack('n', $message->id);
|
||||
|
||||
$flags = 0x00;
|
||||
$flags = ($flags << 1) | ($message->qr ? 1 : 0);
|
||||
$flags = ($flags << 4) | $message->opcode;
|
||||
$flags = ($flags << 1) | ($message->aa ? 1 : 0);
|
||||
$flags = ($flags << 1) | ($message->tc ? 1 : 0);
|
||||
$flags = ($flags << 1) | ($message->rd ? 1 : 0);
|
||||
$flags = ($flags << 1) | ($message->ra ? 1 : 0);
|
||||
$flags = ($flags << 3) | 0; // skip unused zero bit
|
||||
$flags = ($flags << 4) | $message->rcode;
|
||||
|
||||
$data .= pack('n', $flags);
|
||||
|
||||
$data .= pack('n', count($message->questions));
|
||||
$data .= pack('n', count($message->answers));
|
||||
$data .= pack('n', count($message->authority));
|
||||
$data .= pack('n', count($message->additional));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Query[] $questions
|
||||
* @return string
|
||||
*/
|
||||
private function questionToBinary(array $questions)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
foreach ($questions as $question) {
|
||||
$data .= $this->domainNameToBinary($question->name);
|
||||
$data .= pack('n*', $question->type, $question->class);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Record[] $records
|
||||
* @return string
|
||||
*/
|
||||
private function recordsToBinary(array $records)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
foreach ($records as $record) {
|
||||
/* @var $record Record */
|
||||
switch ($record->type) {
|
||||
case Message::TYPE_A:
|
||||
case Message::TYPE_AAAA:
|
||||
$binary = \inet_pton($record->data);
|
||||
break;
|
||||
case Message::TYPE_CNAME:
|
||||
case Message::TYPE_NS:
|
||||
case Message::TYPE_PTR:
|
||||
$binary = $this->domainNameToBinary($record->data);
|
||||
break;
|
||||
case Message::TYPE_TXT:
|
||||
case Message::TYPE_SPF:
|
||||
$binary = $this->textsToBinary($record->data);
|
||||
break;
|
||||
case Message::TYPE_MX:
|
||||
$binary = \pack(
|
||||
'n',
|
||||
$record->data['priority']
|
||||
);
|
||||
$binary .= $this->domainNameToBinary($record->data['target']);
|
||||
break;
|
||||
case Message::TYPE_SRV:
|
||||
$binary = \pack(
|
||||
'n*',
|
||||
$record->data['priority'],
|
||||
$record->data['weight'],
|
||||
$record->data['port']
|
||||
);
|
||||
$binary .= $this->domainNameToBinary($record->data['target']);
|
||||
break;
|
||||
case Message::TYPE_SOA:
|
||||
$binary = $this->domainNameToBinary($record->data['mname']);
|
||||
$binary .= $this->domainNameToBinary($record->data['rname']);
|
||||
$binary .= \pack(
|
||||
'N*',
|
||||
$record->data['serial'],
|
||||
$record->data['refresh'],
|
||||
$record->data['retry'],
|
||||
$record->data['expire'],
|
||||
$record->data['minimum']
|
||||
);
|
||||
break;
|
||||
case Message::TYPE_CAA:
|
||||
$binary = \pack(
|
||||
'C*',
|
||||
$record->data['flag'],
|
||||
\strlen($record->data['tag'])
|
||||
);
|
||||
$binary .= $record->data['tag'];
|
||||
$binary .= $record->data['value'];
|
||||
break;
|
||||
case Message::TYPE_SSHFP:
|
||||
$binary = \pack(
|
||||
'CCH*',
|
||||
$record->data['algorithm'],
|
||||
$record->data['type'],
|
||||
$record->data['fingerprint']
|
||||
);
|
||||
break;
|
||||
case Message::TYPE_OPT:
|
||||
$binary = '';
|
||||
foreach ($record->data as $opt => $value) {
|
||||
if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) {
|
||||
$value = \pack('n', round($value * 10));
|
||||
}
|
||||
$binary .= \pack('n*', $opt, \strlen((string) $value)) . $value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// RDATA is already stored as binary value for unknown record types
|
||||
$binary = $record->data;
|
||||
}
|
||||
|
||||
$data .= $this->domainNameToBinary($record->name);
|
||||
$data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
|
||||
$data .= $binary;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $texts
|
||||
* @return string
|
||||
*/
|
||||
private function textsToBinary(array $texts)
|
||||
{
|
||||
$data = '';
|
||||
foreach ($texts as $text) {
|
||||
$data .= \chr(\strlen($text)) . $text;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $host
|
||||
* @return string
|
||||
*/
|
||||
private function domainNameToBinary($host)
|
||||
{
|
||||
if ($host === '') {
|
||||
return "\0";
|
||||
}
|
||||
|
||||
// break up domain name at each dot that is not preceeded by a backslash (escaped notation)
|
||||
return $this->textsToBinary(
|
||||
\array_map(
|
||||
'stripcslashes',
|
||||
\preg_split(
|
||||
'/(?<!\\\\)\./',
|
||||
$host . '.'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
356
vendor/react/dns/src/Protocol/Parser.php
vendored
Normal file
356
vendor/react/dns/src/Protocol/Parser.php
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Protocol;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Dns\Query\Query;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* DNS protocol parser
|
||||
*
|
||||
* Obsolete and uncommon types and classes are not implemented.
|
||||
*/
|
||||
final class Parser
|
||||
{
|
||||
/**
|
||||
* Parses the given raw binary message into a Message object
|
||||
*
|
||||
* @param string $data
|
||||
* @throws InvalidArgumentException
|
||||
* @return Message
|
||||
*/
|
||||
public function parseMessage($data)
|
||||
{
|
||||
$message = $this->parse($data, 0);
|
||||
if ($message === null) {
|
||||
throw new InvalidArgumentException('Unable to parse binary message');
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @return ?Message
|
||||
*/
|
||||
private function parse($data, $consumed)
|
||||
{
|
||||
if (!isset($data[12 - 1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12)));
|
||||
|
||||
$message = new Message();
|
||||
$message->id = $id;
|
||||
$message->rcode = $fields & 0xf;
|
||||
$message->ra = (($fields >> 7) & 1) === 1;
|
||||
$message->rd = (($fields >> 8) & 1) === 1;
|
||||
$message->tc = (($fields >> 9) & 1) === 1;
|
||||
$message->aa = (($fields >> 10) & 1) === 1;
|
||||
$message->opcode = ($fields >> 11) & 0xf;
|
||||
$message->qr = (($fields >> 15) & 1) === 1;
|
||||
$consumed += 12;
|
||||
|
||||
// parse all questions
|
||||
for ($i = $qdCount; $i > 0; --$i) {
|
||||
list($question, $consumed) = $this->parseQuestion($data, $consumed);
|
||||
if ($question === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->questions[] = $question;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all answer records
|
||||
for ($i = $anCount; $i > 0; --$i) {
|
||||
list($record, $consumed) = $this->parseRecord($data, $consumed);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->answers[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all authority records
|
||||
for ($i = $nsCount; $i > 0; --$i) {
|
||||
list($record, $consumed) = $this->parseRecord($data, $consumed);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->authority[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all additional records
|
||||
for ($i = $arCount; $i > 0; --$i) {
|
||||
list($record, $consumed) = $this->parseRecord($data, $consumed);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->additional[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @return array
|
||||
*/
|
||||
private function parseQuestion($data, $consumed)
|
||||
{
|
||||
list($labels, $consumed) = $this->readLabels($data, $consumed);
|
||||
|
||||
if ($labels === null || !isset($data[$consumed + 4 - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
return array(
|
||||
new Query(
|
||||
implode('.', $labels),
|
||||
$type,
|
||||
$class
|
||||
),
|
||||
$consumed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @return array An array with a parsed Record on success or array with null if data is invalid/incomplete
|
||||
*/
|
||||
private function parseRecord($data, $consumed)
|
||||
{
|
||||
list($name, $consumed) = $this->readDomain($data, $consumed);
|
||||
|
||||
if ($name === null || !isset($data[$consumed + 10 - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
list($ttl) = array_values(unpack('N', substr($data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
// TTL is a UINT32 that must not have most significant bit set for BC reasons
|
||||
if ($ttl < 0 || $ttl >= 1 << 31) {
|
||||
$ttl = 0;
|
||||
}
|
||||
|
||||
list($rdLength) = array_values(unpack('n', substr($data, $consumed, 2)));
|
||||
$consumed += 2;
|
||||
|
||||
if (!isset($data[$consumed + $rdLength - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$rdata = null;
|
||||
$expected = $consumed + $rdLength;
|
||||
|
||||
if (Message::TYPE_A === $type) {
|
||||
if ($rdLength === 4) {
|
||||
$rdata = inet_ntop(substr($data, $consumed, $rdLength));
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
} elseif (Message::TYPE_AAAA === $type) {
|
||||
if ($rdLength === 16) {
|
||||
$rdata = inet_ntop(substr($data, $consumed, $rdLength));
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
} elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
|
||||
list($rdata, $consumed) = $this->readDomain($data, $consumed);
|
||||
} elseif (Message::TYPE_TXT === $type || Message::TYPE_SPF === $type) {
|
||||
$rdata = array();
|
||||
while ($consumed < $expected) {
|
||||
$len = ord($data[$consumed]);
|
||||
$rdata[] = (string)substr($data, $consumed + 1, $len);
|
||||
$consumed += $len + 1;
|
||||
}
|
||||
} elseif (Message::TYPE_MX === $type) {
|
||||
if ($rdLength > 2) {
|
||||
list($priority) = array_values(unpack('n', substr($data, $consumed, 2)));
|
||||
list($target, $consumed) = $this->readDomain($data, $consumed + 2);
|
||||
|
||||
$rdata = array(
|
||||
'priority' => $priority,
|
||||
'target' => $target
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SRV === $type) {
|
||||
if ($rdLength > 6) {
|
||||
list($priority, $weight, $port) = array_values(unpack('n*', substr($data, $consumed, 6)));
|
||||
list($target, $consumed) = $this->readDomain($data, $consumed + 6);
|
||||
|
||||
$rdata = array(
|
||||
'priority' => $priority,
|
||||
'weight' => $weight,
|
||||
'port' => $port,
|
||||
'target' => $target
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SSHFP === $type) {
|
||||
if ($rdLength > 2) {
|
||||
list($algorithm, $hash) = \array_values(\unpack('C*', \substr($data, $consumed, 2)));
|
||||
$fingerprint = \bin2hex(\substr($data, $consumed + 2, $rdLength - 2));
|
||||
$consumed += $rdLength;
|
||||
|
||||
$rdata = array(
|
||||
'algorithm' => $algorithm,
|
||||
'type' => $hash,
|
||||
'fingerprint' => $fingerprint
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SOA === $type) {
|
||||
list($mname, $consumed) = $this->readDomain($data, $consumed);
|
||||
list($rname, $consumed) = $this->readDomain($data, $consumed);
|
||||
|
||||
if ($mname !== null && $rname !== null && isset($data[$consumed + 20 - 1])) {
|
||||
list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($data, $consumed, 20)));
|
||||
$consumed += 20;
|
||||
|
||||
$rdata = array(
|
||||
'mname' => $mname,
|
||||
'rname' => $rname,
|
||||
'serial' => $serial,
|
||||
'refresh' => $refresh,
|
||||
'retry' => $retry,
|
||||
'expire' => $expire,
|
||||
'minimum' => $minimum
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_OPT === $type) {
|
||||
$rdata = array();
|
||||
while (isset($data[$consumed + 4 - 1])) {
|
||||
list($code, $length) = array_values(unpack('n*', substr($data, $consumed, 4)));
|
||||
$value = (string) substr($data, $consumed + 4, $length);
|
||||
if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') {
|
||||
$value = null;
|
||||
} elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) {
|
||||
list($value) = array_values(unpack('n', $value));
|
||||
$value = round($value * 0.1, 1);
|
||||
} elseif ($code === Message::OPT_TCP_KEEPALIVE) {
|
||||
break;
|
||||
}
|
||||
$rdata[$code] = $value;
|
||||
$consumed += 4 + $length;
|
||||
}
|
||||
} elseif (Message::TYPE_CAA === $type) {
|
||||
if ($rdLength > 3) {
|
||||
list($flag, $tagLength) = array_values(unpack('C*', substr($data, $consumed, 2)));
|
||||
|
||||
if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) {
|
||||
$tag = substr($data, $consumed + 2, $tagLength);
|
||||
$value = substr($data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength);
|
||||
$consumed += $rdLength;
|
||||
|
||||
$rdata = array(
|
||||
'flag' => $flag,
|
||||
'tag' => $tag,
|
||||
'value' => $value
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// unknown types simply parse rdata as an opaque binary string
|
||||
$rdata = substr($data, $consumed, $rdLength);
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
|
||||
// ensure parsing record data consumes expact number of bytes indicated in record length
|
||||
if ($consumed !== $expected || $rdata === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
return array(
|
||||
new Record($name, $type, $class, $ttl, $rdata),
|
||||
$consumed
|
||||
);
|
||||
}
|
||||
|
||||
private function readDomain($data, $consumed)
|
||||
{
|
||||
list ($labels, $consumed) = $this->readLabels($data, $consumed);
|
||||
|
||||
if ($labels === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
// use escaped notation for each label part, then join using dots
|
||||
return array(
|
||||
\implode(
|
||||
'.',
|
||||
\array_map(
|
||||
function ($label) {
|
||||
return \addcslashes($label, "\0..\40.\177");
|
||||
},
|
||||
$labels
|
||||
)
|
||||
),
|
||||
$consumed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion
|
||||
* @return array
|
||||
*/
|
||||
private function readLabels($data, $consumed, $compressionDepth = 127)
|
||||
{
|
||||
$labels = array();
|
||||
|
||||
while (true) {
|
||||
if (!isset($data[$consumed])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$length = \ord($data[$consumed]);
|
||||
|
||||
// end of labels reached
|
||||
if ($length === 0) {
|
||||
$consumed += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// first two bits set? this is a compressed label (14 bit pointer offset)
|
||||
if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1]) && $compressionDepth) {
|
||||
$offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
|
||||
if ($offset >= $consumed) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$consumed += 2;
|
||||
list($newLabels) = $this->readLabels($data, $offset, $compressionDepth - 1);
|
||||
|
||||
if ($newLabels === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$labels = array_merge($labels, $newLabels);
|
||||
break;
|
||||
}
|
||||
|
||||
// length MUST be 0-63 (6 bits only) and data has to be large enough
|
||||
if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$labels[] = substr($data, $consumed + 1, $length);
|
||||
$consumed += $length + 1;
|
||||
}
|
||||
|
||||
return array($labels, $consumed);
|
||||
}
|
||||
}
|
||||
88
vendor/react/dns/src/Query/CachingExecutor.php
vendored
Normal file
88
vendor/react/dns/src/Query/CachingExecutor.php
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Cache\CacheInterface;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class CachingExecutor implements ExecutorInterface
|
||||
{
|
||||
/**
|
||||
* Default TTL for negative responses (NXDOMAIN etc.).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const TTL = 60;
|
||||
|
||||
private $executor;
|
||||
private $cache;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, CacheInterface $cache)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$id = $query->name . ':' . $query->type . ':' . $query->class;
|
||||
$cache = $this->cache;
|
||||
$that = $this;
|
||||
$executor = $this->executor;
|
||||
|
||||
$pending = $cache->get($id);
|
||||
return new Promise(function ($resolve, $reject) use ($query, $id, $cache, $executor, &$pending, $that) {
|
||||
$pending->then(
|
||||
function ($message) use ($query, $id, $cache, $executor, &$pending, $that) {
|
||||
// return cached response message on cache hit
|
||||
if ($message !== null) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
// perform DNS lookup if not already cached
|
||||
return $pending = $executor->query($query)->then(
|
||||
function (Message $message) use ($cache, $id, $that) {
|
||||
// DNS response message received => store in cache when not truncated and return
|
||||
if (!$message->tc) {
|
||||
$cache->set($id, $message, $that->ttl($message));
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
);
|
||||
}
|
||||
)->then($resolve, function ($e) use ($reject, &$pending) {
|
||||
$reject($e);
|
||||
$pending = null;
|
||||
});
|
||||
}, function ($_, $reject) use (&$pending, $query) {
|
||||
$reject(new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled'));
|
||||
$pending->cancel();
|
||||
$pending = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return int
|
||||
* @internal
|
||||
*/
|
||||
public function ttl(Message $message)
|
||||
{
|
||||
// select TTL from answers (should all be the same), use smallest value if available
|
||||
// @link https://tools.ietf.org/html/rfc2181#section-5.2
|
||||
$ttl = null;
|
||||
foreach ($message->answers as $answer) {
|
||||
if ($ttl === null || $answer->ttl < $ttl) {
|
||||
$ttl = $answer->ttl;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ttl === null) {
|
||||
$ttl = self::TTL;
|
||||
}
|
||||
|
||||
return $ttl;
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/Query/CancellationException.php
vendored
Normal file
7
vendor/react/dns/src/Query/CancellationException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
final class CancellationException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
91
vendor/react/dns/src/Query/CoopExecutor.php
vendored
Normal file
91
vendor/react/dns/src/Query/CoopExecutor.php
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Promise;
|
||||
|
||||
/**
|
||||
* Cooperatively resolves hosts via the given base executor to ensure same query is not run concurrently
|
||||
*
|
||||
* Wraps an existing `ExecutorInterface` to keep tracking of pending queries
|
||||
* and only starts a new query when the same query is not already pending. Once
|
||||
* the underlying query is fulfilled/rejected, it will forward its value to all
|
||||
* promises awaiting the same query.
|
||||
*
|
||||
* This means it will not limit concurrency for queries that differ, for example
|
||||
* when sending many queries for different host names or types.
|
||||
*
|
||||
* This is useful because all executors are entirely async and as such allow you
|
||||
* to execute any number of queries concurrently. You should probably limit the
|
||||
* number of concurrent queries in your application or you're very likely going
|
||||
* to face rate limitations and bans on the resolver end. For many common
|
||||
* applications, you may want to avoid sending the same query multiple times
|
||||
* when the first one is still pending, so you will likely want to use this in
|
||||
* combination with some other executor like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new RetryExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
final class CoopExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $pending = array();
|
||||
private $counts = array();
|
||||
|
||||
public function __construct(ExecutorInterface $base)
|
||||
{
|
||||
$this->executor = $base;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$key = $this->serializeQueryToIdentity($query);
|
||||
if (isset($this->pending[$key])) {
|
||||
// same query is already pending, so use shared reference to pending query
|
||||
$promise = $this->pending[$key];
|
||||
++$this->counts[$key];
|
||||
} else {
|
||||
// no such query pending, so start new query and keep reference until it's fulfilled or rejected
|
||||
$promise = $this->executor->query($query);
|
||||
$this->pending[$key] = $promise;
|
||||
$this->counts[$key] = 1;
|
||||
|
||||
$pending =& $this->pending;
|
||||
$counts =& $this->counts;
|
||||
$promise->then(function () use ($key, &$pending, &$counts) {
|
||||
unset($pending[$key], $counts[$key]);
|
||||
}, function () use ($key, &$pending, &$counts) {
|
||||
unset($pending[$key], $counts[$key]);
|
||||
});
|
||||
}
|
||||
|
||||
// Return a child promise awaiting the pending query.
|
||||
// Cancelling this child promise should only cancel the pending query
|
||||
// when no other child promise is awaiting the same query.
|
||||
$pending =& $this->pending;
|
||||
$counts =& $this->counts;
|
||||
return new Promise(function ($resolve, $reject) use ($promise) {
|
||||
$promise->then($resolve, $reject);
|
||||
}, function () use (&$promise, $key, $query, &$pending, &$counts) {
|
||||
if (--$counts[$key] < 1) {
|
||||
unset($pending[$key], $counts[$key]);
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
}
|
||||
throw new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled');
|
||||
});
|
||||
}
|
||||
|
||||
private function serializeQueryToIdentity(Query $query)
|
||||
{
|
||||
return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
|
||||
}
|
||||
}
|
||||
43
vendor/react/dns/src/Query/ExecutorInterface.php
vendored
Normal file
43
vendor/react/dns/src/Query/ExecutorInterface.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
interface ExecutorInterface
|
||||
{
|
||||
/**
|
||||
* Executes a query and will return a response message
|
||||
*
|
||||
* It returns a Promise which either fulfills with a response
|
||||
* `React\Dns\Model\Message` on success or rejects with an `Exception` if
|
||||
* the query is not successful. A response message may indicate an error
|
||||
* condition in its `rcode`, but this is considered a valid response message.
|
||||
*
|
||||
* ```php
|
||||
* $executor->query($query)->then(
|
||||
* function (React\Dns\Model\Message $response) {
|
||||
* // response message successfully received
|
||||
* var_dump($response->rcode, $response->answers);
|
||||
* },
|
||||
* function (Exception $error) {
|
||||
* // failed to query due to $error
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* The returned Promise MUST be implemented in such a way that it can be
|
||||
* cancelled when it is still pending. Cancelling a pending promise MUST
|
||||
* reject its value with an Exception. It SHOULD clean up any underlying
|
||||
* resources and references as applicable.
|
||||
*
|
||||
* ```php
|
||||
* $promise = $executor->query($query);
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param Query $query
|
||||
* @return \React\Promise\PromiseInterface<\React\Dns\Model\Message>
|
||||
* resolves with response message on success or rejects with an Exception on error
|
||||
*/
|
||||
public function query(Query $query);
|
||||
}
|
||||
49
vendor/react/dns/src/Query/FallbackExecutor.php
vendored
Normal file
49
vendor/react/dns/src/Query/FallbackExecutor.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class FallbackExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $fallback;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, ExecutorInterface $fallback)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->fallback = $fallback;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$cancelled = false;
|
||||
$fallback = $this->fallback;
|
||||
$promise = $this->executor->query($query);
|
||||
|
||||
return new Promise(function ($resolve, $reject) use (&$promise, $fallback, $query, &$cancelled) {
|
||||
$promise->then($resolve, function (\Exception $e1) use ($fallback, $query, $resolve, $reject, &$cancelled, &$promise) {
|
||||
// reject if primary resolution rejected due to cancellation
|
||||
if ($cancelled) {
|
||||
$reject($e1);
|
||||
return;
|
||||
}
|
||||
|
||||
// start fallback query if primary query rejected
|
||||
$promise = $fallback->query($query)->then($resolve, function (\Exception $e2) use ($e1, $reject) {
|
||||
$append = $e2->getMessage();
|
||||
if (($pos = strpos($append, ':')) !== false) {
|
||||
$append = substr($append, $pos + 2);
|
||||
}
|
||||
|
||||
// reject with combined error message if both queries fail
|
||||
$reject(new \RuntimeException($e1->getMessage() . '. ' . $append));
|
||||
});
|
||||
});
|
||||
}, function () use (&$promise, &$cancelled) {
|
||||
// cancel pending query (primary or fallback)
|
||||
$cancelled = true;
|
||||
$promise->cancel();
|
||||
});
|
||||
}
|
||||
}
|
||||
89
vendor/react/dns/src/Query/HostsFileExecutor.php
vendored
Normal file
89
vendor/react/dns/src/Query/HostsFileExecutor.php
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Config\HostsFile;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Promise;
|
||||
|
||||
/**
|
||||
* Resolves hosts from the given HostsFile or falls back to another executor
|
||||
*
|
||||
* If the host is found in the hosts file, it will not be passed to the actual
|
||||
* DNS executor. If the host is not found in the hosts file, it will be passed
|
||||
* to the DNS executor as a fallback.
|
||||
*/
|
||||
final class HostsFileExecutor implements ExecutorInterface
|
||||
{
|
||||
private $hosts;
|
||||
private $fallback;
|
||||
|
||||
public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
|
||||
{
|
||||
$this->hosts = $hosts;
|
||||
$this->fallback = $fallback;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
|
||||
// forward lookup for type A or AAAA
|
||||
$records = array();
|
||||
$expectsColon = $query->type === Message::TYPE_AAAA;
|
||||
foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
|
||||
// ensure this is an IPv4/IPV6 address according to query type
|
||||
if ((strpos($ip, ':') !== false) === $expectsColon) {
|
||||
$records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
|
||||
}
|
||||
}
|
||||
|
||||
if ($records) {
|
||||
return Promise\resolve(
|
||||
Message::createResponseWithAnswersForQuery($query, $records)
|
||||
);
|
||||
}
|
||||
} elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
|
||||
// reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
|
||||
$ip = $this->getIpFromHost($query->name);
|
||||
|
||||
if ($ip !== null) {
|
||||
$records = array();
|
||||
foreach ($this->hosts->getHostsForIp($ip) as $host) {
|
||||
$records[] = new Record($query->name, $query->type, $query->class, 0, $host);
|
||||
}
|
||||
|
||||
if ($records) {
|
||||
return Promise\resolve(
|
||||
Message::createResponseWithAnswersForQuery($query, $records)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fallback->query($query);
|
||||
}
|
||||
|
||||
private function getIpFromHost($host)
|
||||
{
|
||||
if (substr($host, -13) === '.in-addr.arpa') {
|
||||
// IPv4: read as IP and reverse bytes
|
||||
$ip = @inet_pton(substr($host, 0, -13));
|
||||
if ($ip === false || isset($ip[4])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return inet_ntop(strrev($ip));
|
||||
} elseif (substr($host, -9) === '.ip6.arpa') {
|
||||
// IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
|
||||
$ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
|
||||
if ($ip === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $ip;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
vendor/react/dns/src/Query/Query.php
vendored
Normal file
69
vendor/react/dns/src/Query/Query.php
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
|
||||
/**
|
||||
* This class represents a single question in a query/response message
|
||||
*
|
||||
* It uses a structure similar to `\React\Dns\Message\Record`, but does not
|
||||
* contain fields for resulting TTL and resulting record data (IPs etc.).
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc1035#section-4.1.2
|
||||
* @see \React\Dns\Message\Record
|
||||
*/
|
||||
final class Query
|
||||
{
|
||||
/**
|
||||
* @var string query name, i.e. hostname to look up
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var int query type (aka QTYPE), see Message::TYPE_* constants
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var int query class (aka QCLASS), see Message::CLASS_IN constant
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @param string $name query name, i.e. hostname to look up
|
||||
* @param int $type query type, see Message::TYPE_* constants
|
||||
* @param int $class query class, see Message::CLASS_IN constant
|
||||
*/
|
||||
public function __construct($name, $type, $class)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the hostname and query type/class for this query
|
||||
*
|
||||
* The output format is supposed to be human readable and is subject to change.
|
||||
* The format is inspired by RFC 3597 when handling unkown types/classes.
|
||||
*
|
||||
* @return string "example.com (A)" or "example.com (CLASS0 TYPE1234)"
|
||||
* @link https://tools.ietf.org/html/rfc3597
|
||||
*/
|
||||
public function describe()
|
||||
{
|
||||
$class = $this->class !== Message::CLASS_IN ? 'CLASS' . $this->class . ' ' : '';
|
||||
|
||||
$type = 'TYPE' . $this->type;
|
||||
$ref = new \ReflectionClass('React\Dns\Model\Message');
|
||||
foreach ($ref->getConstants() as $name => $value) {
|
||||
if ($value === $this->type && \strpos($name, 'TYPE_') === 0) {
|
||||
$type = \substr($name, 5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->name . ' (' . $class . $type . ')';
|
||||
}
|
||||
}
|
||||
85
vendor/react/dns/src/Query/RetryExecutor.php
vendored
Normal file
85
vendor/react/dns/src/Query/RetryExecutor.php
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
final class RetryExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $retries;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, $retries = 2)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->retries = $retries;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
return $this->tryQuery($query, $this->retries);
|
||||
}
|
||||
|
||||
public function tryQuery(Query $query, $retries)
|
||||
{
|
||||
$deferred = new Deferred(function () use (&$promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
});
|
||||
|
||||
$success = function ($value) use ($deferred, &$errorback) {
|
||||
$errorback = null;
|
||||
$deferred->resolve($value);
|
||||
};
|
||||
|
||||
$executor = $this->executor;
|
||||
$errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries, $executor) {
|
||||
if (!$e instanceof TimeoutException) {
|
||||
$errorback = null;
|
||||
$deferred->reject($e);
|
||||
} elseif ($retries <= 0) {
|
||||
$errorback = null;
|
||||
$deferred->reject($e = new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: too many retries',
|
||||
0,
|
||||
$e
|
||||
));
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
|
||||
// Exception trace arguments are not available on some PHP 7.4 installs
|
||||
// @codeCoverageIgnoreStart
|
||||
foreach ($trace as $ti => $one) {
|
||||
if (isset($one['args'])) {
|
||||
foreach ($one['args'] as $ai => $arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$r->setValue($e, $trace);
|
||||
} else {
|
||||
--$retries;
|
||||
$promise = $executor->query($query)->then(
|
||||
$success,
|
||||
$errorback
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
$promise = $this->executor->query($query)->then(
|
||||
$success,
|
||||
$errorback
|
||||
);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
85
vendor/react/dns/src/Query/SelectiveTransportExecutor.php
vendored
Normal file
85
vendor/react/dns/src/Query/SelectiveTransportExecutor.php
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Promise;
|
||||
|
||||
/**
|
||||
* Send DNS queries over a UDP or TCP/IP stream transport.
|
||||
*
|
||||
* This class will automatically choose the correct transport protocol to send
|
||||
* a DNS query to your DNS server. It will always try to send it over the more
|
||||
* efficient UDP transport first. If this query yields a size related issue
|
||||
* (truncated messages), it will retry over a streaming TCP/IP transport.
|
||||
*
|
||||
* For more advanced usages one can utilize this class directly.
|
||||
* The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
*
|
||||
* ```php
|
||||
* $executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
|
||||
*
|
||||
* $executor->query(
|
||||
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
* )->then(function (Message $message) {
|
||||
* foreach ($message->answers as $answer) {
|
||||
* echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
* }
|
||||
* }, 'printf');
|
||||
* ```
|
||||
*
|
||||
* Note that this executor only implements the logic to select the correct
|
||||
* transport for the given DNS query. Implementing the correct transport logic,
|
||||
* implementing timeouts and any retry logic is left up to the given executors,
|
||||
* see also [`UdpTransportExecutor`](#udptransportexecutor) and
|
||||
* [`TcpTransportExecutor`](#tcptransportexecutor) for more details.
|
||||
*
|
||||
* Note that this executor is entirely async and as such allows you to execute
|
||||
* any number of queries concurrently. You should probably limit the number of
|
||||
* concurrent queries in your application or you're very likely going to face
|
||||
* rate limitations and bans on the resolver end. For many common applications,
|
||||
* you may want to avoid sending the same query multiple times when the first
|
||||
* one is still pending, so you will likely want to use this in combination with
|
||||
* a `CoopExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new SelectiveTransportExecutor(
|
||||
* $datagramExecutor,
|
||||
* $streamExecutor
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
class SelectiveTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $datagramExecutor;
|
||||
private $streamExecutor;
|
||||
|
||||
public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterface $streamExecutor)
|
||||
{
|
||||
$this->datagramExecutor = $datagramExecutor;
|
||||
$this->streamExecutor = $streamExecutor;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$stream = $this->streamExecutor;
|
||||
$pending = $this->datagramExecutor->query($query);
|
||||
|
||||
return new Promise(function ($resolve, $reject) use (&$pending, $stream, $query) {
|
||||
$pending->then(
|
||||
$resolve,
|
||||
function ($e) use (&$pending, $stream, $query, $resolve, $reject) {
|
||||
if ($e->getCode() === (\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90)) {
|
||||
$pending = $stream->query($query)->then($resolve, $reject);
|
||||
} else {
|
||||
$reject($e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}, function () use (&$pending) {
|
||||
$pending->cancel();
|
||||
$pending = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
382
vendor/react/dns/src/Query/TcpTransportExecutor.php
vendored
Normal file
382
vendor/react/dns/src/Query/TcpTransportExecutor.php
vendored
Normal file
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Dns\Protocol\Parser;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
/**
|
||||
* Send DNS queries over a TCP/IP stream transport.
|
||||
*
|
||||
* This is one of the main classes that send a DNS query to your DNS server.
|
||||
*
|
||||
* For more advanced usages one can utilize this class directly.
|
||||
* The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
*
|
||||
* ```php
|
||||
* $executor = new TcpTransportExecutor('8.8.8.8:53');
|
||||
*
|
||||
* $executor->query(
|
||||
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
* )->then(function (Message $message) {
|
||||
* foreach ($message->answers as $answer) {
|
||||
* echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
* }
|
||||
* }, 'printf');
|
||||
* ```
|
||||
*
|
||||
* See also [example #92](examples).
|
||||
*
|
||||
* Note that this executor does not implement a timeout, so you will very likely
|
||||
* want to use this in combination with a `TimeoutExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new TimeoutExecutor(
|
||||
* new TcpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
|
||||
* transport, so you do not necessarily have to implement any retry logic.
|
||||
*
|
||||
* Note that this executor is entirely async and as such allows you to execute
|
||||
* queries concurrently. The first query will establish a TCP/IP socket
|
||||
* connection to the DNS server which will be kept open for a short period.
|
||||
* Additional queries will automatically reuse this existing socket connection
|
||||
* to the DNS server, will pipeline multiple requests over this single
|
||||
* connection and will keep an idle connection open for a short period. The
|
||||
* initial TCP/IP connection overhead may incur a slight delay if you only send
|
||||
* occasional queries – when sending a larger number of concurrent queries over
|
||||
* an existing connection, it becomes increasingly more efficient and avoids
|
||||
* creating many concurrent sockets like the UDP-based executor. You may still
|
||||
* want to limit the number of (concurrent) queries in your application or you
|
||||
* may be facing rate limitations and bans on the resolver end. For many common
|
||||
* applications, you may want to avoid sending the same query multiple times
|
||||
* when the first one is still pending, so you will likely want to use this in
|
||||
* combination with a `CoopExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new TcpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* > Internally, this class uses PHP's TCP/IP sockets and does not take advantage
|
||||
* of [react/socket](https://github.com/reactphp/socket) purely for
|
||||
* organizational reasons to avoid a cyclic dependency between the two
|
||||
* packages. Higher-level components should take advantage of the Socket
|
||||
* component instead of reimplementing this socket logic from scratch.
|
||||
*/
|
||||
class TcpTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $nameserver;
|
||||
private $loop;
|
||||
private $parser;
|
||||
private $dumper;
|
||||
|
||||
/**
|
||||
* @var ?resource
|
||||
*/
|
||||
private $socket;
|
||||
|
||||
/**
|
||||
* @var Deferred[]
|
||||
*/
|
||||
private $pending = array();
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $names = array();
|
||||
|
||||
/**
|
||||
* Maximum idle time when socket is current unused (i.e. no pending queries outstanding)
|
||||
*
|
||||
* If a new query is to be sent during the idle period, we can reuse the
|
||||
* existing socket without having to wait for a new socket connection.
|
||||
* This uses a rather small, hard-coded value to not keep any unneeded
|
||||
* sockets open and to not keep the loop busy longer than needed.
|
||||
*
|
||||
* A future implementation may take advantage of `edns-tcp-keepalive` to keep
|
||||
* the socket open for longer periods. This will likely require explicit
|
||||
* configuration because this may consume additional resources and also keep
|
||||
* the loop busy for longer than expected in some applications.
|
||||
*
|
||||
* @var float
|
||||
* @link https://tools.ietf.org/html/rfc7766#section-6.2.1
|
||||
* @link https://tools.ietf.org/html/rfc7828
|
||||
*/
|
||||
private $idlePeriod = 0.001;
|
||||
|
||||
/**
|
||||
* @var ?\React\EventLoop\TimerInterface
|
||||
*/
|
||||
private $idleTimer;
|
||||
|
||||
private $writeBuffer = '';
|
||||
private $writePending = false;
|
||||
|
||||
private $readBuffer = '';
|
||||
private $readPending = false;
|
||||
|
||||
/** @var string */
|
||||
private $readChunk = 0xffff;
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct($nameserver, $loop = null)
|
||||
{
|
||||
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
|
||||
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
|
||||
$nameserver = '[' . $nameserver . ']';
|
||||
}
|
||||
|
||||
$parts = \parse_url((\strpos($nameserver, '://') === false ? 'tcp://' : '') . $nameserver);
|
||||
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'tcp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
|
||||
throw new \InvalidArgumentException('Invalid nameserver address given');
|
||||
}
|
||||
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->nameserver = 'tcp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->parser = new Parser();
|
||||
$this->dumper = new BinaryDumper();
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$request = Message::createRequestForQuery($query);
|
||||
|
||||
// keep shuffing message ID to avoid using the same message ID for two pending queries at the same time
|
||||
while (isset($this->pending[$request->id])) {
|
||||
$request->id = \mt_rand(0, 0xffff); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$queryData = $this->dumper->toBinary($request);
|
||||
$length = \strlen($queryData);
|
||||
if ($length > 0xffff) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Query too large for TCP transport'
|
||||
));
|
||||
}
|
||||
|
||||
$queryData = \pack('n', $length) . $queryData;
|
||||
|
||||
if ($this->socket === null) {
|
||||
// create async TCP/IP connection (may take a while)
|
||||
$socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT);
|
||||
if ($socket === false) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
// set socket to non-blocking and wait for it to become writable (connection success/rejected)
|
||||
\stream_set_blocking($socket, false);
|
||||
if (\function_exists('stream_set_chunk_size')) {
|
||||
\stream_set_chunk_size($socket, $this->readChunk); // @codeCoverageIgnore
|
||||
}
|
||||
$this->socket = $socket;
|
||||
}
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
|
||||
// wait for socket to become writable to actually write out data
|
||||
$this->writeBuffer .= $queryData;
|
||||
if (!$this->writePending) {
|
||||
$this->writePending = true;
|
||||
$this->loop->addWriteStream($this->socket, array($this, 'handleWritable'));
|
||||
}
|
||||
|
||||
$names =& $this->names;
|
||||
$that = $this;
|
||||
$deferred = new Deferred(function () use ($that, &$names, $request) {
|
||||
// remove from list of pending names, but remember pending query
|
||||
$name = $names[$request->id];
|
||||
unset($names[$request->id]);
|
||||
$that->checkIdle();
|
||||
|
||||
throw new CancellationException('DNS query for ' . $name . ' has been cancelled');
|
||||
});
|
||||
|
||||
$this->pending[$request->id] = $deferred;
|
||||
$this->names[$request->id] = $query->describe();
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function handleWritable()
|
||||
{
|
||||
if ($this->readPending === false) {
|
||||
$name = @\stream_socket_get_name($this->socket, true);
|
||||
if ($name === false) {
|
||||
// Connection failed? Check socket error if available for underlying errno/errstr.
|
||||
// @codeCoverageIgnoreStart
|
||||
if (\function_exists('socket_import_stream')) {
|
||||
$socket = \socket_import_stream($this->socket);
|
||||
$errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
|
||||
$errstr = \socket_strerror($errno);
|
||||
} else {
|
||||
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
|
||||
$errstr = 'Connection refused';
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$this->closeError('Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', $errno);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->readPending = true;
|
||||
$this->loop->addReadStream($this->socket, array($this, 'handleRead'));
|
||||
}
|
||||
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Match errstr from PHP's warning message.
|
||||
// fwrite(): Send of 327712 bytes failed with errno=32 Broken pipe
|
||||
\preg_match('/errno=(\d+) (.+)/', $error, $m);
|
||||
$errno = isset($m[1]) ? (int) $m[1] : 0;
|
||||
$errstr = isset($m[2]) ? $m[2] : $error;
|
||||
});
|
||||
|
||||
$written = \fwrite($this->socket, $this->writeBuffer);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if ($written === false || $written === 0) {
|
||||
$this->closeError(
|
||||
'Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->writeBuffer[$written])) {
|
||||
$this->writeBuffer = \substr($this->writeBuffer, $written);
|
||||
} else {
|
||||
$this->loop->removeWriteStream($this->socket);
|
||||
$this->writePending = false;
|
||||
$this->writeBuffer = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function handleRead()
|
||||
{
|
||||
// read one chunk of data from the DNS server
|
||||
// any error is fatal, this is a stream of TCP/IP data
|
||||
$chunk = @\fread($this->socket, $this->readChunk);
|
||||
if ($chunk === false || $chunk === '') {
|
||||
$this->closeError('Connection to DNS server ' . $this->nameserver . ' lost');
|
||||
return;
|
||||
}
|
||||
|
||||
// reassemble complete message by concatenating all chunks.
|
||||
$this->readBuffer .= $chunk;
|
||||
|
||||
// response message header contains at least 12 bytes
|
||||
while (isset($this->readBuffer[11])) {
|
||||
// read response message length from first 2 bytes and ensure we have length + data in buffer
|
||||
list(, $length) = \unpack('n', $this->readBuffer);
|
||||
if (!isset($this->readBuffer[$length + 1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = \substr($this->readBuffer, 2, $length);
|
||||
$this->readBuffer = (string)substr($this->readBuffer, $length + 2);
|
||||
|
||||
try {
|
||||
$response = $this->parser->parseMessage($data);
|
||||
} catch (\Exception $e) {
|
||||
// reject all pending queries if we received an invalid message from remote server
|
||||
$this->closeError('Invalid message received from DNS server ' . $this->nameserver);
|
||||
return;
|
||||
}
|
||||
|
||||
// reject all pending queries if we received an unexpected response ID or truncated response
|
||||
if (!isset($this->pending[$response->id]) || $response->tc) {
|
||||
$this->closeError('Invalid response message received from DNS server ' . $this->nameserver);
|
||||
return;
|
||||
}
|
||||
|
||||
$deferred = $this->pending[$response->id];
|
||||
unset($this->pending[$response->id], $this->names[$response->id]);
|
||||
|
||||
$deferred->resolve($response);
|
||||
|
||||
$this->checkIdle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param string $reason
|
||||
* @param int $code
|
||||
*/
|
||||
public function closeError($reason, $code = 0)
|
||||
{
|
||||
$this->readBuffer = '';
|
||||
if ($this->readPending) {
|
||||
$this->loop->removeReadStream($this->socket);
|
||||
$this->readPending = false;
|
||||
}
|
||||
|
||||
$this->writeBuffer = '';
|
||||
if ($this->writePending) {
|
||||
$this->loop->removeWriteStream($this->socket);
|
||||
$this->writePending = false;
|
||||
}
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
|
||||
@\fclose($this->socket);
|
||||
$this->socket = null;
|
||||
|
||||
foreach ($this->names as $id => $name) {
|
||||
$this->pending[$id]->reject(new \RuntimeException(
|
||||
'DNS query for ' . $name . ' failed: ' . $reason,
|
||||
$code
|
||||
));
|
||||
}
|
||||
$this->pending = $this->names = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function checkIdle()
|
||||
{
|
||||
if ($this->idleTimer === null && !$this->names) {
|
||||
$that = $this;
|
||||
$this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () use ($that) {
|
||||
$that->closeError('Idle timeout');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/Query/TimeoutException.php
vendored
Normal file
7
vendor/react/dns/src/Query/TimeoutException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
final class TimeoutException extends \Exception
|
||||
{
|
||||
}
|
||||
78
vendor/react/dns/src/Query/TimeoutExecutor.php
vendored
Normal file
78
vendor/react/dns/src/Query/TimeoutExecutor.php
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class TimeoutExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $loop;
|
||||
private $timeout;
|
||||
|
||||
/**
|
||||
* @param ExecutorInterface $executor
|
||||
* @param float $timeout
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct(ExecutorInterface $executor, $timeout, $loop = null)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->executor = $executor;
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$promise = $this->executor->query($query);
|
||||
|
||||
$loop = $this->loop;
|
||||
$time = $this->timeout;
|
||||
return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $query) {
|
||||
$timer = null;
|
||||
$promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
|
||||
if ($timer) {
|
||||
$loop->cancelTimer($timer);
|
||||
}
|
||||
$timer = false;
|
||||
$resolve($v);
|
||||
}, function ($v) use (&$timer, $loop, $reject) {
|
||||
if ($timer) {
|
||||
$loop->cancelTimer($timer);
|
||||
}
|
||||
$timer = false;
|
||||
$reject($v);
|
||||
});
|
||||
|
||||
// promise already resolved => no need to start timer
|
||||
if ($timer === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start timeout timer which will cancel the pending promise
|
||||
$timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $query) {
|
||||
$reject(new TimeoutException(
|
||||
'DNS query for ' . $query->describe() . ' timed out'
|
||||
));
|
||||
|
||||
// Cancel pending query to clean up any underlying resources and references.
|
||||
// Avoid garbage references in call stack by passing pending promise by reference.
|
||||
assert(\method_exists($promise, 'cancel'));
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
});
|
||||
}, function () use (&$promise) {
|
||||
// Cancelling this promise will cancel the pending query, thus triggering the rejection logic above.
|
||||
// Avoid garbage references in call stack by passing pending promise by reference.
|
||||
assert(\method_exists($promise, 'cancel'));
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
221
vendor/react/dns/src/Query/UdpTransportExecutor.php
vendored
Normal file
221
vendor/react/dns/src/Query/UdpTransportExecutor.php
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Dns\Protocol\Parser;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
/**
|
||||
* Send DNS queries over a UDP transport.
|
||||
*
|
||||
* This is the main class that sends a DNS query to your DNS server and is used
|
||||
* internally by the `Resolver` for the actual message transport.
|
||||
*
|
||||
* For more advanced usages one can utilize this class directly.
|
||||
* The following example looks up the `IPv6` address for `igor.io`.
|
||||
*
|
||||
* ```php
|
||||
* $executor = new UdpTransportExecutor('8.8.8.8:53');
|
||||
*
|
||||
* $executor->query(
|
||||
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
* )->then(function (Message $message) {
|
||||
* foreach ($message->answers as $answer) {
|
||||
* echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
* }
|
||||
* }, 'printf');
|
||||
* ```
|
||||
*
|
||||
* See also the [fourth example](examples).
|
||||
*
|
||||
* Note that this executor does not implement a timeout, so you will very likely
|
||||
* want to use this in combination with a `TimeoutExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Also note that this executor uses an unreliable UDP transport and that it
|
||||
* does not implement any retry logic, so you will likely want to use this in
|
||||
* combination with a `RetryExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new RetryExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Note that this executor is entirely async and as such allows you to execute
|
||||
* any number of queries concurrently. You should probably limit the number of
|
||||
* concurrent queries in your application or you're very likely going to face
|
||||
* rate limitations and bans on the resolver end. For many common applications,
|
||||
* you may want to avoid sending the same query multiple times when the first
|
||||
* one is still pending, so you will likely want to use this in combination with
|
||||
* a `CoopExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new RetryExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* > Internally, this class uses PHP's UDP sockets and does not take advantage
|
||||
* of [react/datagram](https://github.com/reactphp/datagram) purely for
|
||||
* organizational reasons to avoid a cyclic dependency between the two
|
||||
* packages. Higher-level components should take advantage of the Datagram
|
||||
* component instead of reimplementing this socket logic from scratch.
|
||||
*/
|
||||
final class UdpTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $nameserver;
|
||||
private $loop;
|
||||
private $parser;
|
||||
private $dumper;
|
||||
|
||||
/**
|
||||
* maximum UDP packet size to send and receive
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxPacketSize = 512;
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct($nameserver, $loop = null)
|
||||
{
|
||||
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
|
||||
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
|
||||
$nameserver = '[' . $nameserver . ']';
|
||||
}
|
||||
|
||||
$parts = \parse_url((\strpos($nameserver, '://') === false ? 'udp://' : '') . $nameserver);
|
||||
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'udp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
|
||||
throw new \InvalidArgumentException('Invalid nameserver address given');
|
||||
}
|
||||
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->nameserver = 'udp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->parser = new Parser();
|
||||
$this->dumper = new BinaryDumper();
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$request = Message::createRequestForQuery($query);
|
||||
|
||||
$queryData = $this->dumper->toBinary($request);
|
||||
if (isset($queryData[$this->maxPacketSize])) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Query too large for UDP transport',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
}
|
||||
|
||||
// UDP connections are instant, so try connection without a loop or timeout
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
$socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0);
|
||||
if ($socket === false) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
// set socket to non-blocking and immediately try to send (fill write buffer)
|
||||
\stream_set_blocking($socket, false);
|
||||
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Write may potentially fail, but most common errors are already caught by connection check above.
|
||||
// Among others, macOS is known to report here when trying to send to broadcast address.
|
||||
// This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data.
|
||||
// fwrite(): send of 8192 bytes failed with errno=111 Connection refused
|
||||
\preg_match('/errno=(\d+) (.+)/', $error, $m);
|
||||
$errno = isset($m[1]) ? (int) $m[1] : 0;
|
||||
$errstr = isset($m[2]) ? $m[2] : $error;
|
||||
});
|
||||
|
||||
$written = \fwrite($socket, $queryData);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if ($written !== \strlen($queryData)) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
$loop = $this->loop;
|
||||
$deferred = new Deferred(function () use ($loop, $socket, $query) {
|
||||
// cancellation should remove socket from loop and close socket
|
||||
$loop->removeReadStream($socket);
|
||||
\fclose($socket);
|
||||
|
||||
throw new CancellationException('DNS query for ' . $query->describe() . ' has been cancelled');
|
||||
});
|
||||
|
||||
$max = $this->maxPacketSize;
|
||||
$parser = $this->parser;
|
||||
$nameserver = $this->nameserver;
|
||||
$loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request, $max, $nameserver) {
|
||||
// try to read a single data packet from the DNS server
|
||||
// ignoring any errors, this is uses UDP packets and not a stream of data
|
||||
$data = @\fread($socket, $max);
|
||||
if ($data === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $parser->parseMessage($data);
|
||||
} catch (\Exception $e) {
|
||||
// ignore and await next if we received an invalid message from remote server
|
||||
// this may as well be a fake response from an attacker (possible DOS)
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore and await next if we received an unexpected response ID
|
||||
// this may as well be a fake response from an attacker (possible cache poisoning)
|
||||
if ($response->id !== $request->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we only react to the first valid message, so remove socket from loop and close
|
||||
$loop->removeReadStream($socket);
|
||||
\fclose($socket);
|
||||
|
||||
if ($response->tc) {
|
||||
$deferred->reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: The DNS server ' . $nameserver . ' returned a truncated result for a UDP query',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$deferred->resolve($response);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/RecordNotFoundException.php
vendored
Normal file
7
vendor/react/dns/src/RecordNotFoundException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns;
|
||||
|
||||
final class RecordNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
226
vendor/react/dns/src/Resolver/Factory.php
vendored
Normal file
226
vendor/react/dns/src/Resolver/Factory.php
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
use React\Cache\ArrayCache;
|
||||
use React\Cache\CacheInterface;
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Config\HostsFile;
|
||||
use React\Dns\Query\CachingExecutor;
|
||||
use React\Dns\Query\CoopExecutor;
|
||||
use React\Dns\Query\ExecutorInterface;
|
||||
use React\Dns\Query\FallbackExecutor;
|
||||
use React\Dns\Query\HostsFileExecutor;
|
||||
use React\Dns\Query\RetryExecutor;
|
||||
use React\Dns\Query\SelectiveTransportExecutor;
|
||||
use React\Dns\Query\TcpTransportExecutor;
|
||||
use React\Dns\Query\TimeoutExecutor;
|
||||
use React\Dns\Query\UdpTransportExecutor;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
final class Factory
|
||||
{
|
||||
/**
|
||||
* Creates a DNS resolver instance for the given DNS config
|
||||
*
|
||||
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
|
||||
* single nameserver address. If the given config contains more than one DNS
|
||||
* nameserver, all DNS nameservers will be used in order. The primary DNS
|
||||
* server will always be used first before falling back to the secondary or
|
||||
* tertiary DNS server.
|
||||
*
|
||||
* @param Config|string $config DNS Config object (recommended) or single nameserver address
|
||||
* @param ?LoopInterface $loop
|
||||
* @return \React\Dns\Resolver\ResolverInterface
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
|
||||
*/
|
||||
public function create($config, $loop = null)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get()));
|
||||
|
||||
return new Resolver($executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cached DNS resolver instance for the given DNS config and cache
|
||||
*
|
||||
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
|
||||
* single nameserver address. If the given config contains more than one DNS
|
||||
* nameserver, all DNS nameservers will be used in order. The primary DNS
|
||||
* server will always be used first before falling back to the secondary or
|
||||
* tertiary DNS server.
|
||||
*
|
||||
* @param Config|string $config DNS Config object (recommended) or single nameserver address
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?CacheInterface $cache
|
||||
* @return \React\Dns\Resolver\ResolverInterface
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
|
||||
*/
|
||||
public function createCached($config, $loop = null, $cache = null)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
if ($cache !== null && !$cache instanceof CacheInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($cache) expected null|React\Cache\CacheInterface');
|
||||
}
|
||||
|
||||
// default to keeping maximum of 256 responses in cache unless explicitly given
|
||||
if (!($cache instanceof CacheInterface)) {
|
||||
$cache = new ArrayCache(256);
|
||||
}
|
||||
|
||||
$executor = $this->createExecutor($config, $loop ?: Loop::get());
|
||||
$executor = new CachingExecutor($executor, $cache);
|
||||
$executor = $this->decorateHostsFileExecutor($executor);
|
||||
|
||||
return new Resolver($executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the hosts file and decorates the given executor on success
|
||||
*
|
||||
* @param ExecutorInterface $executor
|
||||
* @return ExecutorInterface
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function decorateHostsFileExecutor(ExecutorInterface $executor)
|
||||
{
|
||||
try {
|
||||
$executor = new HostsFileExecutor(
|
||||
HostsFile::loadFromPathBlocking(),
|
||||
$executor
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
// ignore this file if it can not be loaded
|
||||
}
|
||||
|
||||
// Windows does not store localhost in hosts file by default but handles this internally
|
||||
// To compensate for this, we explicitly use hard-coded defaults for localhost
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
$executor = new HostsFileExecutor(
|
||||
new HostsFile("127.0.0.1 localhost\n::1 localhost"),
|
||||
$executor
|
||||
);
|
||||
}
|
||||
|
||||
return $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config|string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return CoopExecutor
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
|
||||
*/
|
||||
private function createExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
if ($nameserver instanceof Config) {
|
||||
if (!$nameserver->nameservers) {
|
||||
throw new \UnderflowException('Empty config with no DNS servers');
|
||||
}
|
||||
|
||||
// Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config).
|
||||
// Note to future self: Recursion isn't too hard, but how deep do we really want to go?
|
||||
$primary = reset($nameserver->nameservers);
|
||||
$secondary = next($nameserver->nameservers);
|
||||
$tertiary = next($nameserver->nameservers);
|
||||
|
||||
if ($tertiary !== false) {
|
||||
// 3 DNS servers given => nest first with fallback for second and third
|
||||
return new CoopExecutor(
|
||||
new RetryExecutor(
|
||||
new FallbackExecutor(
|
||||
$this->createSingleExecutor($primary, $loop),
|
||||
new FallbackExecutor(
|
||||
$this->createSingleExecutor($secondary, $loop),
|
||||
$this->createSingleExecutor($tertiary, $loop)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} elseif ($secondary !== false) {
|
||||
// 2 DNS servers given => fallback from first to second
|
||||
return new CoopExecutor(
|
||||
new RetryExecutor(
|
||||
new FallbackExecutor(
|
||||
$this->createSingleExecutor($primary, $loop),
|
||||
$this->createSingleExecutor($secondary, $loop)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// 1 DNS server given => use single executor
|
||||
$nameserver = $primary;
|
||||
}
|
||||
}
|
||||
|
||||
return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return ExecutorInterface
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
*/
|
||||
private function createSingleExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
$parts = \parse_url($nameserver);
|
||||
|
||||
if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
|
||||
$executor = $this->createTcpExecutor($nameserver, $loop);
|
||||
} elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') {
|
||||
$executor = $this->createUdpExecutor($nameserver, $loop);
|
||||
} else {
|
||||
$executor = new SelectiveTransportExecutor(
|
||||
$this->createUdpExecutor($nameserver, $loop),
|
||||
$this->createTcpExecutor($nameserver, $loop)
|
||||
);
|
||||
}
|
||||
|
||||
return $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return TimeoutExecutor
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
*/
|
||||
private function createTcpExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
return new TimeoutExecutor(
|
||||
new TcpTransportExecutor($nameserver, $loop),
|
||||
5.0,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return TimeoutExecutor
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
*/
|
||||
private function createUdpExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
return new TimeoutExecutor(
|
||||
new UdpTransportExecutor(
|
||||
$nameserver,
|
||||
$loop
|
||||
),
|
||||
5.0,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
}
|
||||
147
vendor/react/dns/src/Resolver/Resolver.php
vendored
Normal file
147
vendor/react/dns/src/Resolver/Resolver.php
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Query\ExecutorInterface;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\RecordNotFoundException;
|
||||
|
||||
/**
|
||||
* @see ResolverInterface for the base interface
|
||||
*/
|
||||
final class Resolver implements ResolverInterface
|
||||
{
|
||||
private $executor;
|
||||
|
||||
public function __construct(ExecutorInterface $executor)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
}
|
||||
|
||||
public function resolve($domain)
|
||||
{
|
||||
return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
|
||||
return $ips[array_rand($ips)];
|
||||
});
|
||||
}
|
||||
|
||||
public function resolveAll($domain, $type)
|
||||
{
|
||||
$query = new Query($domain, $type, Message::CLASS_IN);
|
||||
$that = $this;
|
||||
|
||||
return $this->executor->query(
|
||||
$query
|
||||
)->then(function (Message $response) use ($query, $that) {
|
||||
return $that->extractValues($query, $response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* [Internal] extract all resource record values from response for this query
|
||||
*
|
||||
* @param Query $query
|
||||
* @param Message $response
|
||||
* @return array
|
||||
* @throws RecordNotFoundException when response indicates an error or contains no data
|
||||
* @internal
|
||||
*/
|
||||
public function extractValues(Query $query, Message $response)
|
||||
{
|
||||
// reject if response code indicates this is an error response message
|
||||
$code = $response->rcode;
|
||||
if ($code !== Message::RCODE_OK) {
|
||||
switch ($code) {
|
||||
case Message::RCODE_FORMAT_ERROR:
|
||||
$message = 'Format Error';
|
||||
break;
|
||||
case Message::RCODE_SERVER_FAILURE:
|
||||
$message = 'Server Failure';
|
||||
break;
|
||||
case Message::RCODE_NAME_ERROR:
|
||||
$message = 'Non-Existent Domain / NXDOMAIN';
|
||||
break;
|
||||
case Message::RCODE_NOT_IMPLEMENTED:
|
||||
$message = 'Not Implemented';
|
||||
break;
|
||||
case Message::RCODE_REFUSED:
|
||||
$message = 'Refused';
|
||||
break;
|
||||
default:
|
||||
$message = 'Unknown error response code ' . $code;
|
||||
}
|
||||
throw new RecordNotFoundException(
|
||||
'DNS query for ' . $query->describe() . ' returned an error response (' . $message . ')',
|
||||
$code
|
||||
);
|
||||
}
|
||||
|
||||
$answers = $response->answers;
|
||||
$addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
|
||||
|
||||
// reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
|
||||
if (0 === count($addresses)) {
|
||||
throw new RecordNotFoundException(
|
||||
'DNS query for ' . $query->describe() . ' did not return a valid answer (NOERROR / NODATA)'
|
||||
);
|
||||
}
|
||||
|
||||
return array_values($addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \React\Dns\Model\Record[] $answers
|
||||
* @param string $name
|
||||
* @param int $type
|
||||
* @return array
|
||||
*/
|
||||
private function valuesByNameAndType(array $answers, $name, $type)
|
||||
{
|
||||
// return all record values for this name and type (if any)
|
||||
$named = $this->filterByName($answers, $name);
|
||||
$records = $this->filterByType($named, $type);
|
||||
if ($records) {
|
||||
return $this->mapRecordData($records);
|
||||
}
|
||||
|
||||
// no matching records found? check if there are any matching CNAMEs instead
|
||||
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
|
||||
if ($cnameRecords) {
|
||||
$cnames = $this->mapRecordData($cnameRecords);
|
||||
foreach ($cnames as $cname) {
|
||||
$records = array_merge(
|
||||
$records,
|
||||
$this->valuesByNameAndType($answers, $cname, $type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
private function filterByName(array $answers, $name)
|
||||
{
|
||||
return $this->filterByField($answers, 'name', $name);
|
||||
}
|
||||
|
||||
private function filterByType(array $answers, $type)
|
||||
{
|
||||
return $this->filterByField($answers, 'type', $type);
|
||||
}
|
||||
|
||||
private function filterByField(array $answers, $field, $value)
|
||||
{
|
||||
$value = strtolower($value);
|
||||
return array_filter($answers, function ($answer) use ($field, $value) {
|
||||
return $value === strtolower($answer->$field);
|
||||
});
|
||||
}
|
||||
|
||||
private function mapRecordData(array $records)
|
||||
{
|
||||
return array_map(function ($record) {
|
||||
return $record->data;
|
||||
}, $records);
|
||||
}
|
||||
}
|
||||
94
vendor/react/dns/src/Resolver/ResolverInterface.php
vendored
Normal file
94
vendor/react/dns/src/Resolver/ResolverInterface.php
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
interface ResolverInterface
|
||||
{
|
||||
/**
|
||||
* Resolves the given $domain name to a single IPv4 address (type `A` query).
|
||||
*
|
||||
* ```php
|
||||
* $resolver->resolve('reactphp.org')->then(function ($ip) {
|
||||
* echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This is one of the main methods in this package. It sends a DNS query
|
||||
* for the given $domain name to your DNS server and returns a single IP
|
||||
* address on success.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that contains more than
|
||||
* one IP address for this query, it will randomly pick one of the IP
|
||||
* addresses from the response. If you want the full list of IP addresses
|
||||
* or want to send a different type of query, you should use the
|
||||
* [`resolveAll()`](#resolveall) method instead.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that indicates an error
|
||||
* code, this method will reject with a `RecordNotFoundException`. Its
|
||||
* message and code can be used to check for the response code.
|
||||
*
|
||||
* If the DNS communication fails and the server does not respond with a
|
||||
* valid response message, this message will reject with an `Exception`.
|
||||
*
|
||||
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
*
|
||||
* ```php
|
||||
* $promise = $resolver->resolve('reactphp.org');
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param string $domain
|
||||
* @return \React\Promise\PromiseInterface<string>
|
||||
* resolves with a single IP address on success or rejects with an Exception on error.
|
||||
*/
|
||||
public function resolve($domain);
|
||||
|
||||
/**
|
||||
* Resolves all record values for the given $domain name and query $type.
|
||||
*
|
||||
* ```php
|
||||
* $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
|
||||
* echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
* });
|
||||
*
|
||||
* $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
||||
* echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This is one of the main methods in this package. It sends a DNS query
|
||||
* for the given $domain name to your DNS server and returns a list with all
|
||||
* record values on success.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that contains one or more
|
||||
* records for this query, it will return a list with all record values
|
||||
* from the response. You can use the `Message::TYPE_*` constants to control
|
||||
* which type of query will be sent. Note that this method always returns a
|
||||
* list of record values, but each record value type depends on the query
|
||||
* type. For example, it returns the IPv4 addresses for type `A` queries,
|
||||
* the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
|
||||
* `CNAME` and `PTR` queries and structured data for other queries. See also
|
||||
* the `Record` documentation for more details.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that indicates an error
|
||||
* code, this method will reject with a `RecordNotFoundException`. Its
|
||||
* message and code can be used to check for the response code.
|
||||
*
|
||||
* If the DNS communication fails and the server does not respond with a
|
||||
* valid response message, this message will reject with an `Exception`.
|
||||
*
|
||||
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
*
|
||||
* ```php
|
||||
* $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param string $domain
|
||||
* @return \React\Promise\PromiseInterface<array>
|
||||
* Resolves with all record values on success or rejects with an Exception on error.
|
||||
*/
|
||||
public function resolveAll($domain, $type);
|
||||
}
|
||||
Reference in New Issue
Block a user