Skip to content

Use default nameservers (support /etc/resolv.conf) #29

Closed

Description

@clue

We should look into reading the system's nameserver configuration rather than asking our users to hardcode their DNS settings.

Not to be confused with #10.

Activity

  1. marty-macfly commented on Oct 4, 2016

    @marty-macfly

    Hello,

    I've tried to do something to work around this not yet available functionality, in reactphp/dns. You're idea is to read the file directly in my case i've based my resolve on the native php function.

    Perhaps my code can help some others, i'm brand new to event programming, promise and other stuff so i'm sure you will see some improvement. If the LocalExecutor Fail we can fall back on the classic Executor.

    I've created a LocalResolversExecutor (Query/LocalResolverExecutor.php) :

    <?php
    namespace React\Dns\Query;
    
    use React\Dns\RecordNotFoundException;
    use React\Dns\Model\Message;
    use React\Dns\Model\Record;
    use React\Dns\Query\ExecutorInterface;
    use React\Dns\Query\Query;
    use React\EventLoop\LoopInterface;
    use React\Promise\Deferred;
    
    class LocalResolverExecutor implements ExecutorInterface
    {
        private $loop;
        private $executor;
    
        public function __construct(LoopInterface $loop, ExecutorInterface $executor, $timeout = 5)
        {
            $this->loop = $loop;
            $this->executor = $executor;
        $this->timeout = $timeout;
        }
    
        public function query($nameserver, Query $query)
        {
            $executor = $this->executor;
            return $this
            ->doQuery($query)
                ->then(null, function () use ($query, $nameserver, $executor) {
                    return $executor->query($nameserver, $query);
                });
        }
    
        public function doQuery($query)
        {
    
            $that = $this;
        $name = $query->name;
    
            $deferred = new Deferred(function ($resolve, $reject) use (&$timer, $name) {
                $reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name)));
                $timer->cancel();
            });
    
            $timer = $this->loop->addTimer($this->timeout, function () use ($name, $deferred) {
                $deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name)));
            });
    
            $this->loop->addTimer(0, function () use ($that, $query, $deferred, $timer) {
    
            $record = gethostbyname($query->name);
    
                $timer->cancel();
    
                if ($record == $query->name) {
                    $deferred->reject(new RecordNotFoundException(sprintf('Unable to resolve %s', $query->name)));
                }
    
            $response = $that->buildResponse($query, [ new Record($query->name, $query->type, $query->class, 5, $record) ]);
    
                $deferred->resolve($response);
            });
    
            return $deferred->promise();
    
        }
    
        public function buildResponse(Query $query, array $records)
        {
            $response = new Message();
            $response->header->set('id', $this->generateId());
            $response->header->set('qr', 1);
            $response->header->set('opcode', Message::OPCODE_QUERY);
            $response->header->set('rd', 1);
            $response->header->set('rcode', Message::RCODE_OK);
            $response->questions[] = new Record($query->name, $query->type, $query->class);
            $response->answers = $records;
            $response->prepare();
            return $response;
        }
    
        protected function generateId()
        {
            return mt_rand(0, 0xffff);
        }
    }

    And update Resolver/Factory.php to use it :

    @@ -11,13 +11,14 @@ use React\Dns\Protocol\Parser;
     use React\Dns\Protocol\BinaryDumper;
     use React\EventLoop\LoopInterface;
     use React\Dns\Query\RetryExecutor;
    +use React\Dns\Query\LocalResolverExecutor;
    
     class Factory
     {
         public function create($nameserver, LoopInterface $loop)
         {
             $nameserver = $this->addPortToServerIfMissing($nameserver);
    -        $executor = $this->createRetryExecutor($loop);
    +        $executor = $this->createLocalResolverExecutor($loop);
    
             return new Resolver($nameserver, $executor);
         }
    @@ -46,7 +47,12 @@ class Factory
    
         protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache)
         {
    -        return new CachedExecutor($this->createRetryExecutor($loop), new RecordCache($cache));
    +        return new CachedExecutor($this->createLocalResolverExecutor($loop), new RecordCache($cache));
    +    }
    +
    +    protected function createLocalResolverExecutor(LoopInterface $loop)
    +    {
    +        return new LocalResolverExecutor($loop, $this->createRetryExecutor($loop));
         }
    
         protected function addPortToServerIfMissing($nameserver)

    Regards,
    Macfly

  2. clue commented on Oct 19, 2016

    @clue
    MemberAuthor

    Thanks for posting this @marty-macfly, but unfortunately your code relies on $record = gethostbyname($query->name); which makes the whole thing blocking.

    This ticket likely requires us to do the following:

    • Find the path to the configuration (resolv.conf vs Windows?)
    • Find a way to load this file without blocking (filesystem component?)
    • Parse the contents and then instantiate as usual
    • Work out a proper user-facing API so you usually don't have to care about any of this
  3. kelunik commented on Jan 4, 2017

    @kelunik

    I just landed here while trying to research how you guys do it on Windows. Windows stores that information in the registry unfortunately. :-(

  4. stefanotorresi commented on Apr 7, 2017

    @stefanotorresi

    we could use reactphp/child-process to launch reg query like amphp/windows-registry does.

  5. kelunik commented on Apr 7, 2017

    @kelunik

    If you build an adapter you could just use that library instead of just recreating it.

  6. stefanotorresi commented on Apr 7, 2017

    @stefanotorresi

    @kelunik good point, this is basically a use case for reactphp/promise#78

  7. kelunik commented on Apr 7, 2017

    @kelunik

    No, async interoperability doesn't matter for a specific case. A common event loop does.

  8. stefanotorresi commented on Apr 8, 2017

    @stefanotorresi

    @kelunik I see both things as somewhat related ;)

  9. kelunik commented on Apr 8, 2017

    @kelunik

    They are, but a common loop makes things work, a common promise API makes things just a little bit easier.

  10. WyriHaximus commented on Apr 8, 2017

    @WyriHaximus
    Member

    Aye, a common promise API/interface/spec is a first step to make that happen

  11. clue commented on Feb 6, 2018

    @clue
    MemberAuthor

    Loading the resolv.conf is implemented in #92. This file is present on most systems and will very likely cover the 90% use case (it is not present on Windows systems).

    I will create a follow-up PR to implement nameserver detection for Windows. I've looked into reading this from nslookup (works, but blocks when network is not available), ipconfig /all (difficult to parse due to localization), registry (non-trivial implementation) and wmic (which I ended up using).

    While this works reasonably well, I still think that consumers will have to supply their own fallback if this "autodection" fails. In my opinion, this is best left up to higher level implementation such as reactphp/socket#90.

  12. added this to the v0.4.13 milestone on Feb 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions