How to use Firefox Add-on to receive DNS records that both of “Operation system DNS” and “DNS over HTTPS server”?

I am trying to use Firefox add-on to detect “DNS spoofing” and notify the user by different extension icon color (Detail requirements are in the bottom).

How do I force add-on to receive DNS records that both of “DoH server” & OS’ DNS resolver?

I wrote a Firefox add-on (https://github.com/kevinbolton/DNSOverHTTPS/tree/master/FirefoxAddon), when

  1. network.trr.mode = 1, 2, 4
    Both responses are “isTRR = fales”
  2. network.trr.mode = 3
    OS DNS responses are promise.message = NS_ERROR_UNKNOWN_HOST

You probably have to directly consume DoH inside your extension and not rely on the Firefox stack there. Which is one of the beauties about DoH, you can just implement it as normal HTTP client.

Please correct me if I am wrong, but I think your idea here is to detect manipulations of the “normal” DNS lookup by repeating every hostname lookup twice, once “normal” and once through HTTPS, raising a flag if the results differ.

There are several problems with that approach, though:

  • it is perfectly fine for DNS lookups from different resolvers to return different results. E.g. if a service has multiple servers in different geolocations, DNS can be used to direct users to the closest server. Since your resolvers can be in different locations, they may return different, but still valid, results. See the screenshot below.
  • If you want to check the behavior of the normal lookup, you shpuldn’t mess with that but instead do your second lookup directly though HTTPS, like @freaktechnik suggested
  • On the other hand, if you do change Firefox to use a trusted resolver, why would you check that with a less trustworthy one?
  • your code is highly susceptible to a race condition

image

OK,
I don’t know how to,
Is any example?

You are right.
1.
Thank you, geolocation is a big issue, I will study more detail for this (like Microsoft DNS policy for Geo-Location).

Any way to do DoH is welcome, I will try NikasG suggestion first.

In case someone doesn’t want to enable TRR. This add-on can notify the user.

If possible, I really want to know what lead to race condition. I am new.

Ok, so your current code is basically this:

function resolved(record) {
    web_site_ip = record.addresses[0];
}

function resolved_trr(record_trr) {
    web_site_ip_trr = record_trr.addresses[0];
        
    if (web_site_ip == web_site_ip_trr) { /* OK */ } else { /* Oh no!*/ }
}

resolving = browser.dns.resolve(message.domain_name, ["disable_trr", "bypass_cache", "disable_ipv6", "canonical_name"]);
resolving.then(resolved, err);

resolving_trr = browser.dns.resolve(message.domain_name, ["bypass_cache", "disable_ipv6", "canonical_name"]);
resolving_trr.then(resolved_trr, err);

The line with the if condition assumes that the resolved callback was called before the resolved_trr callback, otherwise you are comparing against an empty web_site_ip value (or that of the previous lookup, because you didn’t declare web_site_ip to be a local variable).

Here is my suggestion how to fix that:

First off, the

doing = browser.something.action();
doing.then(onDone, onError);

is a total anti-pattern. It entirely defeats the point of using promises in the first place.
I do not know why the hell all examples on MDN use that pattern.
Instead, you should use await for the value, and catch for errors, where needed.

The following is easyer to understaind and runs everythin in sequence, thus avoiding race conditions:

record = await browser.dns.resolve(message.domain_name, ["disable_trr", "bypass_cache", "disable_ipv6", "canonical_name"]);
record_trr = await browser.dns.resolve(message.domain_name, ["bypass_cache", "disable_ipv6", "canonical_name"]);

if (web_site_ip == web_site_ip_trr) { /* OK */ } else { /* Oh no!*/ }

Bus, as I said, this runs everything in sequence. It first completes one lookup, then starts the other. To start both at once and then wait for booth to resolve, do this:

[ record, record_trr, ] = await Promise.all([
    browser.dns.resolve(message.domain_name, ["disable_trr", "bypass_cache", "disable_ipv6", "canonical_name"]),
    browser.dns.resolve(message.domain_name, ["bypass_cache", "disable_ipv6", "canonical_name"]),
]);
if (web_site_ip == web_site_ip_trr) { /* OK */ } else { /* Oh no!*/ }

Promise.all takes a list of parallel operations and combines them to one operation. await gets the results, which we directly split up again.

But there is one more problem here: you are not declaring any variables, which means that all the variables will implicitly be global variables. That is pretty messy and can quickly lead to problems if you do multiple things at one (which sooner or later will happen).

Since (during a single lookup) you don’t need to change the values in these variables, yu should declare them as const:

const [ record, record_trr, ] = await Promise.all([
    browser.dns.resolve(message.domain_name, ["disable_trr", "bypass_cache", "disable_ipv6", "canonical_name"]),
    browser.dns.resolve(message.domain_name, ["bypass_cache", "disable_ipv6", "canonical_name"]),
]);
if (web_site_ip == web_site_ip_trr) { /* OK */ } else { /* Oh no!*/ }

In order to be actually allowed to use the await keyword, this code has to be placed in a async function:

async function handleMessage(message) {
    const [ record, record_trr, ] = await Promise.all([
        browser.dns.resolve(message.domain_name, ["disable_trr", "bypass_cache", "disable_ipv6", "canonical_name"]),
        browser.dns.resolve(message.domain_name, ["bypass_cache", "disable_ipv6", "canonical_name"]),
    ]);
    if (web_site_ip == web_site_ip_trr) { /* OK */ } else { /* Oh no!*/ }
}

It is furthermore a (very) good practice to wrap the code of every file in a self-invoking function (to avoid variable naming conflicts between code in different files), to use strict mode and to catch errors:

(function(global) { 'use strict';

async function handleMessage(message) { try {
    const [ record, record_trr, ] = await Promise.all([
        browser.dns.resolve(message.domain_name, ["disable_trr", "bypass_cache", "disable_ipv6", "canonical_name"]),
        browser.dns.resolve(message.domain_name, ["bypass_cache", "disable_ipv6", "canonical_name"]),
    ]);
    if (web_site_ip == web_site_ip_trr) { /* OK */ } else { /* Oh no!*/ }
} catch (error) {
    console.error('dammit', error);
} }

browser.runtime.onMessage.addListener(handleMessage);

})(this);

Thank so much.
I just receive a Bible from God.
It’s really useful to me.