0

I have an issue with my Node.JS application.
For some reason, dns.reverse is only working with the first nameserver specified and ignoring the second one.

I have the following piece of code:

import Resolver from '~/classes/resolver';

Resolver.addNameServer('192.168.2.1', '192.168.1.254').boot();

Resolver.hostnameFromIP('192.168.1.30').then((hostname: string) => {
    console.log(hostname);
}).catch((error) => {
    console.log(error); // We are going to end up here as the 192.168.2.1 is the first and only nameserver which will be queried
});

And here is Resolver class:

import q, { Deferred } from 'q';
import dns from 'dns';
import { ResolverInterface } from '~/classes/resolver/interfaces';

/**
 * Resolver class
 */
class Resolver implements ResolverInterface {

    /**
     * List of nameservers used for hostname resolution
     * @type { string[] }
     */
    public nameservers: string[] = [];

    /**
     * Add nameserver for resolver to use
     * @param { string } nameserver
     * @return { Resolver }
     */
    public addNameServer(...nameserver: string[]) : Resolver {
        this.nameservers.push(...nameserver);
        return this;
    }

    /**
     * Initialize resolver
     * @return { void }
     */
    public boot() : void {
        if (this.nameservers.length === 0) {
            this.initializeWithDefaultNameServers();
        }
        dns.setServers(this.nameservers);
    }

    /**
     * Resolve hostname from the IP address
     * @param { string } ip
     * @return { q.Promise<string> }
     */
    public hostnameFromIP(ip: string) : q.Promise<string> {
        const deferred: Deferred<any> = q.defer();
        dns.reverse(ip, (error: NodeJS.ErrnoException | null, hostnames: string[]) => {
            const defaultErrorMessage: string = 'Unable to resolve hostname';
            if (error) {
                return deferred.reject(defaultErrorMessage);
            }
            let hostname: string = hostnames.length === 0 ? defaultErrorMessage : hostnames[0];
            hostname = hostname.replace('.localdomain', '').trim();
            deferred.resolve(hostname);
        });
        return deferred.promise as q.Promise<string>;
    }

    /**
     * Initialize resolver with default nameservers
     * @return { void }
     * @private
     */
    private initializeWithDefaultNameServers() : void {
        const nameservers: string[] = [
            '192.168.0.1',
            '192.168.1.1',
            '192.168.2.1',
        ];
        nameservers.forEach((nameserver: string) : void => {
            this.nameservers.push(nameserver);
        });
    }

}



export default new Resolver;

Expected behavior: Application should go through all specified nameservers to resolve the hostname for specified IP address

Actual behavior: Depending on which nameserver is first, only that nameserver will be queried for the hostname.
If 192.168.2.1 is first, i can query data for 192.168.2.10, but i cannot do that for 192.168.1.30.
If 192.168.1.254 is first, i can query data for 192.168.1.30, but i cannot do that for 192.168.2.10.

Is there a way to use all specified nameservers while doing reverse hostname lookup using dns.reverse in Node.JS?

Thanks for help to Jorge Fuentes González, here is the version i've ended up using, at least it works for 2 nameservers.

     /**
     * Resolve hostname from IP address
     * @param { string } ip
     * @return { Promise<string> }
     */
    public async hostnameFromIP(ip: string) : Promise<string> {
        return await this.resolveForHostnameFromIP(ip)
            .then((hostname: string): string => hostname)
            .catch(async (error: string): Promise<string> => {
                const indexOf: number = this.nameservers.indexOf(this.currentNameServer);
                const newNameserverIndex: number = indexOf + 1;
                if (newNameserverIndex <= this.nameservers.length - 1) {
                    this.currentNameServer = this.nameservers[indexOf + 1];
                    this.setCurrentNameServerValue();
                    return await this.hostnameFromIP(ip).then((hostname: string): string => hostname);
                }
                return error;
            });
    }

    /**
     * Helper method to resolve hostname from the IP address
     * @param { string } ip
     * @return { q.Promise<string> }
     */
    private resolveForHostnameFromIP(ip: string) : q.Promise<string> {
        const deferred: Deferred<any> = q.defer();
        this.resolver.reverse(ip, (error: NodeJS.ErrnoException | null, hostnames: string[]) => {
            const defaultErrorMessage: string = 'Unable to resolve hostname';
            if (error) {
                return deferred.reject(defaultErrorMessage);
            } else {
                let hostname: string = hostnames.length === 0 ? defaultErrorMessage : hostnames[0];
                hostname = hostname.replace('.localdomain', '').trim();
                deferred.resolve(hostname);
            }
        });
        return deferred.promise as q.Promise<string>;
    }

    /**
     * Update resolver configuration with current name server
     * @return { void }
     * @private
     */
    private setCurrentNameServerValue() : void {
        this.resolver.setServers([this.currentNameServer]);
    };
Ivan Zhivolupov
  • 1,107
  • 2
  • 20
  • 39

1 Answers1

1

Sorry, got the question wrong so this part is not important. Proper answer below. Btw keeping it here just in case someone has a problem like this:
That's not how DNS server lists work in almost any platform. If you look in your system network settings (Windows, Mac or Linux), surely you will see 2 nameservers (as is the default amount all ISPs provide). That's because there is a main server and a fallback server if the main one fails. You system won't return 2 different IPs when you are browsing Internet. That's weird. What your browser want is just an IP to connect to, not a list of IPs. NodeJS works the same. The rest of nameservers are just fallback ones just in case the main one fails.

Now the proper part:
When a nameserver replies with NOTFOUND, nodejs will not continue to the next nameserver, as is actually a working nameserver. Fallback nameservers are only used when the nameserver fails (with a weird error/response or a timeout. Documentation link below with this info).

To achieve what you want you need to set your nameserver, make your query, wait for the response, set the next nameserver, make your query, etc... with your current code must be an easy task. Looks pretty.

Be careful as you must not change the nameservers when a DNS query is in progress as you can read in the documentation.

Jorge Fuentes González
  • 11,568
  • 4
  • 44
  • 64
  • But here is the thing. Inability to resolve IP to a hostname should be considered as a failure (that's why, for example, when steamcache is set up on the network, primary IP is a steamcache IP and secondary is the fallback DNS server). So by failing to resolve IP to hostname from first nameserver, it should fallback to the next one. Isn't that right? But yeah, that makes sense as it even stated in the NodeJS docs, and well, that's the way resolve.conf works, so... – Ivan Zhivolupov Aug 30 '20 at 20:26
  • @IvanZhivolupov Yeah, that's the common sense thing, but as the documentation says, a `NOTFOUND` query will not continue to the next nameserver, as is not a non-working nameserver. Fallback nameservers are only used when the first one fails completely. Is so common to think that way, that they stated it in the documentation haha. You can check if you received a `NOTFOUND` error and go for the next nameserver, as you know that is not how nodejs works. Maybe there's a npm package that do this for you, but will for sure use the `setServers` loop method, or use a third party website. – Jorge Fuentes González Aug 30 '20 at 20:29
  • @IvanZhivolupov `NOTFOUND` is not error this a regular. DNS system is based on that all nameservers in the Net have the same information assuming propagation and caching. So, if one namersever contains information about some host and another server does not, that means that one of nameservers is misconfigured. The problem should addressed to system administrators of the nameservers. – Yuri Ginsburg Aug 30 '20 at 22:23