Use Custom DNS resolver for any request in NodeJS
Asked Answered
D

3

7

I'm looking to find a way to use a custom DNS resolver for a nodejs request using node-fetch. I think there is a star of explaining here : Node override request IP resolution but I can't manage to make it work for any request. My goal is to use an alternative DNS resolver, such as cloudflare (1.1.1.1) or Google public DNS (8.8.8.8) instead the OS / ISP default DNS resolution.

import http from "http";
import https from "https";
import fetch from "node-fetch";

const staticLookup = (ip: string, v?: number) => (hostname: string, _: null, cb: Function) => {
  cb(null, ip, v || 4);
};

const staticDnsAgent = (scheme: "http" | "https", ip: string) => {
  const httpModule = scheme === "http" ? http : https;
  return new httpModule.Agent({ lookup: staticLookup(ip), rejectUnauthorized: false });
};

// Both request are not working
fetch(`https://api.github.com/search/issues?q=Hello%20World`, {
  agent: staticDnsAgent("https", "1.1.1.1")
})

fetch(`http://jsonplaceholder.typicode.com/todos`, {
  agent: staticDnsAgent("http", "8.8.8.8")
})

I'm struggling a bit to find a way to make this example work, I'm pretty sure I have to use the nodejs DNS module and set a custom server.

Departmentalize answered 11/2, 2022 at 1:16 Comment(2)
staticDnsAgent is meant to provide the IP of the domain, so in your example, api.github.com is resolved as 1.1.1.1. Instead, resolve with github.com/sc0Vu/doh-js-client and use the result inside staticDNSAgentGarget
Thanks @Garget I was able to achieve result, see answer below. I used nodejs build in dns module instead external oneDepartmentalize
D
8

Thanks to Martheen who answered in my first post I was able to achieve the result here :

import http from "http";
import https from "https";
import dns from "dns/promises";
import fetch from "node-fetch";

// Cloud flare dns
dns.setServers([
  "1.1.1.1",
  "[2606:4700:4700::1111]",
]);

const staticLookup = () => async (hostname: string, _: null, cb: Function) => {
  const ips = await dns.resolve(hostname);

  if (ips.length === 0) {
    throw new Error(`Unable to resolve ${hostname}`);
  }

  cb(null, ips[0], 4);
};

const staticDnsAgent = (scheme: "http" | "https") => {
  const httpModule = scheme === "http" ? http : https;
  return new httpModule.Agent({ lookup: staticLookup() });
};

fetch(`https://api.github.com/search/issues?q=Hello%20World`, {
  agent: staticDnsAgent("https")
})

fetch(`http://jsonplaceholder.typicode.com/todos`, {
  agent: staticDnsAgent("http")
})

Departmentalize answered 11/2, 2022 at 2:22 Comment(5)
return new httpModule.Agent({ lookup: staticLookup() }); should be ` return new httpModule.Agent({ lookup: staticLookup });`Alarum
I solved the issue with this answer and this post: #35026631Serrato
I ended up with this error when trying your solution Error: queryTxt ENOTFOUND localhost ErrorCaptureStackTrace(err); at QueryReqWrap.onresolve [as oncomplete] (node:internal/dns/promises:240:17) at QueryReqWrap.callbackTrampoline (node:internal/async_hooks:130:17) { And now Im running out of ideasIncision
I mean it's expected localhost is not a domain name in registrars and only exists locally on your PC DNS records. When you use a custom DNS resolver it's external of your PC and bypassing OS DNS resolver which is the pointDepartmentalize
Note that you may want to use (new dns.Resolver()).setServers to avoid globally changing the dns servers on the default global dns object.Disposed
M
2

If you tried the accepted answer's solution, and you get an error saying something like "Invalid IP address: undefined", make sure to check the second argument's all property (_.all). If it is true, you must pass the IPs + family in array form like this (cb(null, [{ address: ip, family: 4 }])). Else, you can pass the IP as per accepted answer.

Source: https://nodejs.org/api/dns.html#dnslookuphostname-options-callback, where it says

With the all option set to true, the arguments for callback change to (err, addresses)

Couldn't comment to the accepted answer, so had to say this as an answer.

Mccauley answered 24/4 at 15:5 Comment(0)
I
1

To use a custom DNS server for all fetch() requests in a Node.js script, you can configure a global request agent using the undici library.

Step 1: Install undici as follows:

npm install undici

Step 2: Set up the global agent with your DNS server configuration:

import { Agent, setGlobalDispatcher } from "undici";

// Replace with your custom DNS server IP
const DnsIp = "127.0.0.1";

const agent = new Agent({
  connect: {
    // The third argument refers to the IP family (4 for IPv4, 6 for IPv6)
    lookup: (_hostname, _options, callback) => callback(null, DnsIp, 4)
  },
});

// Set the custom agent as the global dispatcher
setGlobalDispatcher(agent);

// Example: Use the DNS server at 127.0.0.1 to resolve git.dev
const response = await fetch("https://git.dev/");

This has been tested with Node.js v18. Note that the node-fetch package is not required.

Inconsequent answered 6/1 at 16:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.