Giter VIP home page Giter VIP logo

php-dns's Introduction

Build Status Total Downloads Coverage Status License Scrutinizer Code Quality PHP CI

PHP-DNS: A DNS Abstraction in PHP

Use Cases

This library might be for you if:

  • You want to be able to query DNS records locally or over HTTPS
  • You want observability into your DNS lookups
  • You want something easy to test / mock in your implementation
  • You want to try several different sources of DNS truth
  • You want to easily extend it or contribute to get more behavior you want!

Installation

composer require remotelyliving/php-dns

Usage

Basic Resolvers can be found in src/Resolvers

These resolvers at the least implement the Resolvers\Interfaces\DNSQuery interface

  • GoogleDNS (uses the GoogleDNS DNS over HTTPS API)
  • CloudFlare (uses the CloudFlare DNS over HTTPS API)
  • LocalSystem (uses the local PHP dns query function)
  • Dig (Can use a specific nameserver per instance but requires the host OS to have dig installed). Based on Spatie DNS
$resolver = new Resolvers\GoogleDNS();

// can query via convenience methods
$records = $resolver->getARecords('google.com'); // returns a collection of DNS A Records

// can also query by any RecordType.
$moreRecords = $resolver->getRecords($hostname, DNSRecordType::TYPE_AAAA);

// can query to see if any resolvers find a record or type.
$resolver->hasRecordType($hostname, $type) // true | false
$resolver->hasRecord($record) // true | false

// This becomes very powerful when used with the Chain Resolver

Chain Resolver

The Chain Resolver can be used to read through DNS Resolvers until an answer is found. Whichever you pass in first is the first Resolver it tries in the call sequence. It implements the same DNSQuery interface as the other resolvers but with an additional feature set found in the Chain interface.

So something like:

$chainResolver = new Chain($cloudFlareResolver, $googleDNSResolver, $localDNSResolver);

That will call the GoogleDNS Resolver first, if no answer is found it will continue on to the LocalSystem Resolver. The default call through strategy is First to Find aka Resolvers\Interfaces\Chain::withFirstResults(): Chain

You can randomly select which Resolver in the chain it tries first too via Resolvers\Interfaces\Chain::randomly(): Chain Example:

$foundRecord = $chainResolver->randomly()->getARecords('facebook.com')->pickFirst();

The above code calls through the resolvers randomly until it finds any non empty answer or has exhausted order the chain.

Lastly, and most expensively, there is Resolvers\Interfaces\Chain::withAllResults(): Chain and Resolvers\Interfaces\Chain::withConsensusResults(): Chain All results will be a merge from all the different sources, useful if you want to see what all is out there. Consensus results will be only the results in common from source to source.

src/Resolvers/Interfaces

// returns the first non empty result set
$chainResolver->withFirstResults()->getARecords('facebook.com'); 

// returns the first non empty result set from a randomly selected resolver
$chainResolver->randomly()->getARecords('facebook.com'); 

// returns only common results between resolvers
$chainResolver->withConsensusResults()->getARecords('facebook.com'); 

// returns all collective responses with duplicates filtered out
$chainResolver->withAllResults()->getARecords('facebook.com'); 

Cached Resolver

If you use a PSR6 cache implementation, feel free to wrap whatever Resolver you want to use in the Cached Resolver. It will take in the the lowest TTL of the record(s) and use that as the cache TTL. You may override that behavior by setting a cache TTL in the constructor.

$cachedResolver = new Resolvers\Cached($cache, $resolverOfChoice);
$cachedResolver->getRecords('facebook.com'); // get from cache if possible or falls back to the wrapped resolver and caches the returned records

If you do not wish to cache empty result answers, you may call through with this additional option:

$cachedResolver->withEmptyResultCachingDisabled()->getARecords('facebook.com');

Entities

Take a look in the src/Entities to see what's available for you to query by and receive.

For records with extra type data, like SOA, TXT, MX, CNAME, and NS there is a data attribute on Entities\DNSRecord that will be set with the proper type.

Reverse Lookup

This is offered via a separate ReverseDNSQuery interface as it is not common or available for every type of DNS Resolver. Only the LocalSystem Resolver implements it.

Observability

All provided resolvers have the ability to add subscribers and listeners. They are directly compatible with symfony/event-dispatcher

All events can be found here: src/Observability/Events

With a good idea of what a subscriber can do with them here: src/Observability/Subscribers

You could decide where you want to stream the events whether its to a log or somewhere else. The events are all safe to json_encode() without extra parsing.

If you want to see how easy it is to wire all this up, check out the repl bootstrap

Logging

All provided resolvers implement Psr\Log\LoggerAwareInterface and have a default NullLogger set at runtime.

Tinkering

Take a look in the Makefile for all the things you can do!

There is a very basic REPL implementation that wires up some Resolvers for you already and pipes events to sterr and stdout

make repl

christians-mbp:php-dns chthomas$ make repl
Psy Shell v0.9.9 (PHP 7.2.8 โ€” cli) by Justin Hileman
>>> ls
Variables: $cachedResolver, $chainResolver, $cloudFlareResolver, $googleDNSResolver, $IOSubscriber, $localSystemResolver, $stdErr, $stdOut
>>> $records = $chainResolver->getARecords('facebook.com')
{
    "dns.query.profiled": {
        "elapsedSeconds": 0.21915197372436523,
        "transactionName": "CloudFlare:facebook.com.:A",
        "peakMemoryUsage": 9517288
    }
}
{
    "dns.queried": {
        "resolver": "CloudFlare",
        "hostname": "facebook.com.",
        "type": "A",
        "records": [
            {
                "hostname": "facebook.com.",
                "type": "A",
                "TTL": 224,
                "class": "IN",
                "IPAddress": "31.13.71.36"
            }
        ],
        "empty": false
    }
}
=> RemotelyLiving\PHPDNS\Entities\DNSRecordCollection {#2370}
>>> $records->pickFirst()->toArray()
=> [
     "hostname" => "facebook.com.",
     "type" => "A",
     "TTL" => 224,
     "class" => "IN",
     "IPAddress" => "31.13.71.36",
   ]
>>> $records = $chainResolver->withConsensusResults()->getRecords('facebook.com', 'TXT')
{
    "dns.query.profiled": {
        "elapsedSeconds": 0.023031949996948242,
        "transactionName": "CloudFlare:facebook.com.:TXT",
        "peakMemoryUsage": 9615080
    }
}
{
    "dns.queried": {
        "resolver": "CloudFlare",
        "hostname": "facebook.com.",
        "type": "TXT",
        "records": [
            {
                "hostname": "facebook.com.",
                "type": "TXT",
                "TTL": 9136,
                "class": "IN",
                "data": "v=spf1 redirect=_spf.facebook.com"
            }
        ],
        "empty": false
    }
}
{
    "dns.query.profiled": {
        "elapsedSeconds": 0.23299598693847656,
        "transactionName": "GoogleDNS:facebook.com.:TXT",
        "peakMemoryUsage": 9615080
    }
}
{
    "dns.queried": {
        "resolver": "GoogleDNS",
        "hostname": "facebook.com.",
        "type": "TXT",
        "records": [
            {
                "hostname": "facebook.com.",
                "type": "TXT",
                "TTL": 21121,
                "class": "IN",
                "data": "v=spf1 redirect=_spf.facebook.com"
            }
        ],
        "empty": false
    }
}
{
    "dns.query.profiled": {
        "elapsedSeconds": 0.0018258094787597656,
        "transactionName": "LocalSystem:facebook.com.:TXT",
        "peakMemoryUsage": 9615080
    }
}
{
    "dns.queried": {
        "resolver": "LocalSystem",
        "hostname": "facebook.com.",
        "type": "TXT",
        "records": [
            {
                "hostname": "facebook.com.",
                "type": "TXT",
                "TTL": 25982,
                "class": "IN",
                "data": "v=spf1 redirect=_spf.facebook.com"
            }
        ],
        "empty": false
    }
}
=> RemotelyLiving\PHPDNS\Entities\DNSRecordCollection {#2413}
>>> $records->pickFirst()->getData()->getValue()
=> "v=spf1 redirect=_spf.facebook.com"
>>> 

php-dns's People

Contributors

joeh avatar lynnverse avatar masroore avatar remotelyliving avatar stephanvierkant avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

php-dns's Issues

Chaining with consensus could lead to unpredictable results

Describe the bug
Chain resolver with consensus might produce inpredictable results when there are some kind of round robin of the results or maybe when the result depends on geolocalisation.

For example looking up A records for google.com with consensus always return an empty result.
However, without chaining and consensus, it returns an unique result that changes (some round robin here) every request.
Also, the result completly differs if using cloudflare or google resolver.

When you do the same tests with for example microsoft.com, multiple A records are returned and the consensus is working.

So I was wondering is there could be a workaround for this or if it's better to drop using the chaining/consensus to avoid possible issues for some domains that would behave like google.com does.

To Reproduce
Steps to reproduce the behavior:

Lookup A record for hostname google.com

use RemotelyLiving\PHPDNS\Resolvers\GoogleDNS;
use RemotelyLiving\PHPDNS\Resolvers\CloudFlare;
use RemotelyLiving\PHPDNS\Resolvers\Chain;

$googleResolver = new GoogleDNS();
$cloudflareResolver = new CloudFlare();
$ipv4Result = $chainResolver->withConsensusResults()->getARecords('google.com');
print_r($ipv4Result);

Result:

RemotelyLiving\PHPDNS\Entities\DNSRecordCollection Object
(
    [records:RemotelyLiving\PHPDNS\Entities\DNSRecordCollection:private] => ArrayIterator Object
        (
            [storage:ArrayIterator:private] => Array
                (
                )

        )

)

Expected behavior
Get a list of A records for the hostname

Google DNS mapper for MX record type

Google does not return MX data like 100 some.host

It has special property for host priority "prio"..

Quick fix for problem is ad those lines to toDNSRecord method:

$value = (isset($this->fields['prio']) && $this->fields['type'] === DNSRecordType::TYPE_MX)
			? $this->fields['prio'] .' '. $this->fields['data']
			: $value;

TXT Record value handling.

I cannot see a way to handle TXT record values. For example, I need to be able to parse SFP records, which are contained in a TXT value in DNS. Am I missing this, or is this on the roadmap for support?

Thanks

PSR-18 migration

Disclaimer: This is a "before I start cutting code in a private fork, let's talk with the author" issues ๐Ÿ˜„

I'd be interested in doing a PR to migrate the request handling to PSR-18, but first, I want to check if it is both desired and on approach.

My immediate sandboxed attempt moves Guzzle to require-dev and also adds a require-dev on symfony/http-client (all requests are asynchronous unless you create a streamed request), which goes to stable release in a few days.

This would mean consumers of php-dns would need to nominate their HTTP Client implementation of choice โ€ฆ but it gives everyone the choice nonetheless.

Plus, obviously, there is going to need to be a major version bump. ๐Ÿ˜‡

Better Unit Tests

The tests as is are very basic. I'd love to add more to cover any corner cases and better test things like testing randomization and concurrency.

Testing more validation cases for entities

Testing IDN's

Complete tests for Cached Resolver

Complete tests for ResolverAbstract

Ability to use this in Symfony 6 (and Laravel 9)

Is your feature request related to a problem? Please describe.
This package is currently not usable in Symfony 6 (or Laravel 9). The spatie/dns 1.x version range requires symfony/process version 5.x. Therefor you won't be able to install this in Symfony 6.

Describe the solution you'd like
We need to upgrade the spatie/dns dependency to at least version 2, which has some breaking changes to the 1.x range. Of course I'm willing to help, but before I put some time and effort into this, I would like to discuss this first.

Describe alternatives you've considered
I've not considered any alternative.

Additional context
When upgrading to spatie/dns version 2, PHP 8 will become the minimum required version of this package. I would like to know if that's acceptable for you.

RemoteSystem Resolver

As I understand it the LocalSystem Resolver relies on the configuration of the local host running the scripts, and cannot connect to say a vendor DNS to check if records are in sync. I frequently need to check DNS propagation and do this by manually checking records across a list of hosts (Google, OpenDNS, Wave, Verizon, etc).

I would like a RemoteResolver that I could pass an IP Address of the DNS server to query, the target to query, and the record type). I could them maybe add these to a chain to see if they are all equal or if some are lagging.

There are some code samples that do this, such as pear/net_dns2 as well I have some code samples of reading the UDP packets themselves.

Is this something you are interested in adding? Do you have any suggestions on how to move forward?

Thanks

Dig resolver incorrect result

Describe the bug

Dig resolver doesn't correctly return result.

To Reproduce

$dig = new Dig(new SpatieDNS(), new DigMapper(), Hostname::createFromString('ns1.yandex.ru'));
$result = $dig->getRecords('yandex.ru',  DNSRecordType::TYPE_A);

$digResponse data is:

yandex.ru.\t\t300 IN A 77.88.55.55\r\n
yandex.ru.\t\t300 IN A 77.88.55.60\r\n
yandex.ru.\t\t300 IN A 5.255.255.55\r\n
yandex.ru.\t\t300 IN A 5.255.255.60\r\n

parseDigResponseToRows(string $digResponse) return $rows is:

array:1 [
  0 => array:5 [
    0 => "yandex.ru.300"
    1 => "IN"
    2 => "A"
    3 => "5.255.255.60yandex.ru.300"
    4 => "IN A 77.88.55.60yandex.ru.300 IN A 5.255.255.55yandex.ru.300 IN A 77.88.55.55"
  ]
]

Expected behavior
Should return correct results.

Thanks!

SRV records

Describe the bug
SRV lookups fail with:
RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException : 0 sipfed.microsoft.com could not be created with type SRV

To Reproduce
$hostname = '_sipfederationtls._tcp.microsoft.com'; $records = $resolver->getRecords($hostname, \RemotelyLiving\PHPDNS\Entities\DNSRecordType::TYPE_SRV);

Expected behavior
nslookup returns:
Non-authoritative answer: _sipfederationtls._tcp.microsoft.com SRV service location: priority = 0 weight = 0 port = 5061 svr hostname = sipfed.microsoft.com
It looks like createFromTypeAndString only handles TXT, NS, CNAME, MX, CAA and SOA records.

if I edit DataAbstract.php to add:
if ($recordType->isA(DNSRecordType::TYPE_SRV)) { return new TXTData($data); }
I see some of the data I am looking for.

I took the TXTData.php and mocked up a SRVData.php file that works and added
if ($recordType->isA(DNSRecordType::TYPE_SRV)) { return new SRVData( $parsed[0] ?? 0, new Hostname($parsed[1]), $parsed[2] ?? 0, $data ); }

Which I think works.

Is this the right direction? Or am I missing something obvious?

Update spatie/dns version

Describe the bug
Hello. Thanks for working on this package.
I am hitting https://github.com/spatie/dns/issues/49 . This issue is already solved in v1.5.0, whereas in php-dns spatie/dns is constrained with ^1.4. Can you please update to at least 1.5.0? Thanks.

To Reproduce
Steps to reproduce the behavior:

$resolver = new Dig();
$result = $resolver->getMXRecords('xn--merkat-1xa.com');

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

Non Blocking Chain Resolver

I'd love to have a Chain Resolver that returns whatever request to a resolver finishes first in a non-blocking way.

Allow passing multiple DNSRecordType's to getRecords

Is your feature request related to a problem? Please describe.

when you'd use dig test.example.com txt and test.example.com is a CNAME record linked to google.com you'd get not only the TXT records but also the CNAME records.

Describe the solution you'd like

Have the possibility to get the records as-is from a lookup

mandrill._domainkey.domain.com. is not a valid hostname

Was trying to check the following format to verify a DKIM setup. The validation part of the RemotelyLiving\PHPDNS\Entities\Hostname invalidates the provided format. Any way to update the validation scheme to allow the above format?

Unable to query PTR, PTRData is missing in DataAbstract factory

I am trying to query for a PTR. Generally, this works for both GoogleDNS and Localsystem Resolvers (we get the correct result from the Resolver), except, when mapping the result, DataAbstract::createFromTypeAndString does not support PTR data. An if-clause for the PTR type is missing, so an exception is thrown.

To Reproduce
Get a new Resolver\GoogleDNS(), create a query for a reverted IP with .in-addr.arpa attached, and with type PTR. An exception is thrown ("RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException : fra16s42-in-f3.1e100.net could not be created with type PTR")

Expected behavior
A DNSRecord containing a PTRData extends DataAbstract object with the PTR query result should be returned. The PTRData object should probably be identical or very similar to the CNAMEData object.

I can make a Pull Request, but maybe there is a reason this isn't implemented?

GoogleDNS: DNS type 46

When using GoogleDNS, I'm having this error:

Notice: Undefined offset: 46

\RemotelyLiving\PHPDNS\Entities\DNSRecordType::CODE_TYPE_MAP hasn't got an RRSIG (int 46) type. Should this be added, or should we ignore 'unknown' types?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.