Skip to content

Commit

Permalink
Merge branch '2.x' into powershell
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/WindowsDnsConfigLoader.php
  • Loading branch information
trowski committed Jan 19, 2025
2 parents e5c6420 + 166c437 commit c7ea180
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 25 deletions.
16 changes: 13 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,21 @@ jobs:
- operating-system: 'ubuntu-latest'
php-version: '8.2'

- operating-system: 'ubuntu-latest'
php-version: '8.3'

- operating-system: 'ubuntu-latest'
php-version: '8.4'
static-analysis: none
style-fix: none
composer-require-checker-version: none

- operating-system: 'windows-latest'
php-version: '8.1'
php-version: '8.3'
job-description: 'on Windows'

- operating-system: 'macos-latest'
php-version: '8.1'
php-version: '8.3'
job-description: 'on macOS'

name: PHP ${{ matrix.php-version }} ${{ matrix.job-description }}
Expand Down Expand Up @@ -72,12 +81,13 @@ jobs:

- name: Run static analysis
run: vendor/bin/psalm.phar
if: matrix.static-analysis != 'none'

- name: Run style fixer
env:
PHP_CS_FIXER_IGNORE_ENV: 1
run: vendor/bin/php-cs-fixer --diff --dry-run -v fix
if: runner.os != 'Windows'
if: runner.os != 'Windows' && matrix.style-fix != 'none'

- name: Install composer-require-checker
run: php -r 'file_put_contents("composer-require-checker.phar", file_get_contents("https://github.com/maglnet/ComposerRequireChecker/releases/download/3.7.0/composer-require-checker.phar"));'
Expand Down
2 changes: 1 addition & 1 deletion src/BlockingFallbackDnsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ final class BlockingFallbackDnsResolver implements DnsResolver
use ForbidCloning;
use ForbidSerialization;

public function resolve(string $name, int $typeRestriction = null, ?Cancellation $cancellation = null): array
public function resolve(string $name, ?int $typeRestriction = null, ?Cancellation $cancellation = null): array
{
if (!\in_array($typeRestriction, [DnsRecord::A, null], true)) {
throw new DnsException("Query for '{$name}' failed, because loading the system's DNS configuration failed and querying records other than A records isn't supported in blocking fallback mode.");
Expand Down
4 changes: 3 additions & 1 deletion src/DnsConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ public function __construct(array $nameservers, array $knownHosts = [])
public function withSearchList(array $searchList): self
{
$self = clone $this;
$self->searchList = $searchList;

// Replace null with '.' for backward compatibility
$self->searchList = \array_map(fn ($search) => $search ?? '.', $searchList);

return $self;
}
Expand Down
32 changes: 19 additions & 13 deletions src/Rfc1035StubDnsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,20 +139,24 @@ public function resolve(string $name, ?int $typeRestriction = null, ?Cancellatio

\assert($this->config !== null);

$searchList = [null];
$searchList = ['.'];
if (!$trailingDot && $dots < $this->config->getNdots()) {
$searchList = \array_merge($this->config->getSearchList(), $searchList);
$configuredSearchList = $this->config->getSearchList();
if (\in_array('.', $configuredSearchList, true)) {
$searchList = $configuredSearchList;
} else {
$searchList = \array_merge($configuredSearchList, $searchList);
}
}

$sendQuery = $this->query(...);

foreach ($searchList as $searchIndex => $search) {
for ($redirects = 0; $redirects < 5; $redirects++) {
$searchName = $name;

if ($search !== null) {
$searchName = $name . "." . $search;
}
$searchName = match ($search) {
'.' => $name,
default => $name . '.' . $search,
};

try {
/** @var non-empty-list<non-empty-list<DnsRecord>> $records */
Expand Down Expand Up @@ -186,13 +190,15 @@ public function resolve(string $name, ?int $typeRestriction = null, ?Cancellatio
}

return \array_merge(...$records);
} catch (MissingDnsRecordException) {
$alias = $this->searchForAliasRecord($searchName, $cancellation);
if ($alias !== null) {
$name = $alias;
}
continue;
} catch (DnsException $e) {
if ($e instanceof MissingDnsRecordException) {
$alias = $this->searchForAliasRecord($searchName, $cancellation);
if ($alias !== null) {
$name = $alias;
continue;
}
}

if ($searchIndex < \count($searchList) - 1 && $this->shouldRetry($e->getCode())) {
continue 2;
}
Expand Down
22 changes: 22 additions & 0 deletions src/StaticDnsConfigLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);

namespace Amp\Dns;

use Amp\ForbidCloning;
use Amp\ForbidSerialization;

final class StaticDnsConfigLoader implements DnsConfigLoader
{
use ForbidCloning;
use ForbidSerialization;

public function __construct(
private readonly DnsConfig $config
) {
}

public function loadConfig(): DnsConfig
{
return $this->config;
}
}
4 changes: 2 additions & 2 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
* @return DnsResolver Returns the application-wide DNS resolver instance
*/
function dnsResolver(DnsResolver $dnsResolver = null): DnsResolver
function dnsResolver(?DnsResolver $dnsResolver = null): DnsResolver
{
static $map;
$map ??= new \WeakMap();
Expand All @@ -38,7 +38,7 @@ function createDefaultResolver(): DnsResolver
*@see DnsResolver::resolve()
*
*/
function resolve(string $name, int $typeRestriction = null, ?Cancellation $cancellation = null): array
function resolve(string $name, ?int $typeRestriction = null, ?Cancellation $cancellation = null): array
{
return dnsResolver()->resolve($name, $typeRestriction, $cancellation);
}
Expand Down
44 changes: 44 additions & 0 deletions test/DnsCacheTrainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php declare(strict_types=1);

namespace Amp\Dns\Test;

use Amp\Cache\Cache;
use Amp\Dns\DnsRecord;
use PHPUnit\Framework\TestCase;

final class DnsCacheTrainer
{
private Cache $mock;

private array $operations = [];

public function __construct(TestCase $testCase)
{
/** @noinspection PhpFieldAssignmentTypeMismatchInspection */
$this->mock = $testCase->getMockBuilder(Cache::class)
->disableOriginalConstructor()
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->getMock();
}

public function givenResponse(DnsRecord ...$records): void
{
$this->mock->method('get')->willReturnCallback(function ($key) use ($records) {
$this->operations[] = ['get', $key];

return \array_map(fn ($record) => [$record->getValue(), $record->getType()], $records);
});
}

public function getOperations(): array
{
return $this->operations;
}

public function getMock(): Cache
{
return $this->mock;
}
}
88 changes: 84 additions & 4 deletions test/Rfc1035StubResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,115 @@

namespace Amp\Dns\Test;

use Amp\Cancellation;
use Amp\Dns\DnsConfig;
use Amp\Dns\DnsException;
use Amp\Dns\DnsRecord;
use Amp\Dns\InvalidNameException;
use Amp\Dns\MissingDnsRecordException;
use Amp\Dns\Rfc1035StubDnsResolver;
use Amp\Dns\StaticDnsConfigLoader;
use Amp\PHPUnit\AsyncTestCase;

class Rfc1035StubResolverTest extends AsyncTestCase
{
private DnsCacheTrainer $cacheTrainer;
private ?DnsConfig $dnsConfig = null;

protected function setUp(): void
{
parent::setUp();

$this->cacheTrainer = new DnsCacheTrainer($this);
}

public function testResolveSecondParameterAcceptedValues(): void
{
$this->expectException(\Error::class);
(new Rfc1035StubDnsResolver)->resolve("abc.de", DnsRecord::TXT);
$this->whenResolve("abc.de", DnsRecord::TXT);
}

public function testIpAsArgumentWithIPv4Restriction(): void
{
$this->expectException(DnsException::class);
(new Rfc1035StubDnsResolver)->resolve("::1", DnsRecord::A);
$this->whenResolve("::1", DnsRecord::A);
}

public function testIpAsArgumentWithIPv6Restriction(): void
{
$this->expectException(DnsException::class);
(new Rfc1035StubDnsResolver)->resolve("127.0.0.1", DnsRecord::AAAA);
$this->whenResolve("127.0.0.1", DnsRecord::AAAA);
}

public function testInvalidName(): void
{
$this->expectException(InvalidNameException::class);
(new Rfc1035StubDnsResolver)->resolve("[email protected]", DnsRecord::A);
$this->whenResolve("[email protected]", DnsRecord::A);
}

public function testSearchListDot(): void
{
$this->dnsConfig = (new DnsConfig(['127.0.0.1']))
->withNdots(1)
->withSearchList(['.']);

$this->cacheTrainer->givenResponse(new DnsRecord('1.2.3.4', DnsRecord::A));

$this->whenResolve('foobar');

$this->assertSame([
['get', 'amphp.dns.foobar#1'],
['get', 'amphp.dns.foobar#28'],
], $this->cacheTrainer->getOperations());
}

public function testSearchListDomain(): void
{
$this->dnsConfig = (new DnsConfig(['127.0.0.1']))
->withNdots(1)
->withSearchList(['search']);

$this->cacheTrainer->givenResponse(new DnsRecord('1.2.3.4', DnsRecord::A));

$this->whenResolve('foobar');

$this->assertSame([
['get', 'amphp.dns.foobar.search#1'],
['get', 'amphp.dns.foobar.search#28'],
], $this->cacheTrainer->getOperations());
}

public function testSearchListDomainDots(): void
{
$this->dnsConfig = (new DnsConfig(['127.0.0.1']))
->withNdots(1)
->withSearchList(['search']);

$this->cacheTrainer->givenResponse(new DnsRecord('1.2.3.4', DnsRecord::A));

$this->whenResolve('foo.bar');

$this->assertSame([
['get', 'amphp.dns.foo.bar#1'],
['get', 'amphp.dns.foo.bar#28'],
], $this->cacheTrainer->getOperations());
}

public function testNonExistingDomain(): void
{
$this->dnsConfig = new DnsConfig(['8.8.8.8:53']);

$this->expectException(MissingDnsRecordException::class);
$this->expectExceptionMessage('No records returned for');

$this->whenResolve('not.existing.domain.com');
}

private function whenResolve(string $name, ?int $typeRestriction = null, ?Cancellation $cancellation = null): void
{
$configLoader = $this->dnsConfig !== null ? new StaticDnsConfigLoader($this->dnsConfig) : null;

$resolver = new Rfc1035StubDnsResolver($this->cacheTrainer->getMock(), $configLoader);
$resolver->resolve($name, $typeRestriction, $cancellation);
}
}
2 changes: 1 addition & 1 deletion test/UdpSocketTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function testMalformedResponse(): void
$socket = Dns\Internal\UdpSocket::connect('udp://' . \stream_socket_get_name($server, false));

$this->expectException(Dns\DnsTimeoutException::class);
$this->expectErrorMessage("Didn't receive a response within 1 seconds, but received 1 invalid packets on this socket");
$this->expectExceptionMessage("Didn't receive a response within 1 seconds, but received 1 invalid packets on this socket");

$socket->ask($question, 1);
}
Expand Down

0 comments on commit c7ea180

Please sign in to comment.