node icon indicating copy to clipboard operation
node copied to clipboard

Subdomains on localhost return ENOTFOUND on some OS.

Open spthiel opened this issue 2 years ago • 9 comments

Version

Tested on v21.2.0, v20.9.0, v18.18.2

Platform

Microsoft Windows NT 10.0.19045.0 x64

Subsystem

No response

What steps will reproduce the bug?

  1. run fetch("http://test.localhost/")

How often does it reproduce? Is there a required condition?

Happens on windows every time

What is the expected behavior? Why is that the expected behavior?

Expected behaviour is ECONNREFUSED or content of what the webpage contains equally to how a node on Linux, cURL or a browser would handle it

What do you see instead?

Uncaught [TypeError: fetch failed] {
  cause: Error: getaddrinfo ENOTFOUND test.localhost
      at GetAddrInfoReqWrap.onlookupall [as complete] (node:dns:118:26)
      at GetAddrInfoReqWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
    errno: -3008,
    code: 'ENOTFOUND',
    syscall: 'getaddrinfo',
    hostname: 'test.localhost'
  }
}

Additional information

Recreated locally in a VM but also on an actual Windows device

spthiel avatar Nov 23 '23 08:11 spthiel

Workaround for this for the time being:

fetch("http://127.0.0.1/", {
    headers: {
        host: "test.localhost"
    }
});

spthiel avatar Nov 24 '23 09:11 spthiel

@marco-ippolito there is the same issue (and workaround) on mac-os

feychenie avatar Mar 27 '24 13:03 feychenie

For those suffering from this issue. I'd say a more future proof solution is to add your "subdomains" in /etc/hosts (I'm not sure where it is in windows)

Add this to hosts

127.0.0.1        test.localhost

That should do the trick :)

cristianoliveira avatar Apr 09 '24 18:04 cristianoliveira

With Node 18.18+ the workaround I posted is no longer possible (because node fetch disallowed setting the host header). Instead use npm i undici

import {request} from "undici";

request("http://127.0.0.1/", {
    headers: {
        host: "test.localhost"
    }
});

spthiel avatar May 17 '24 11:05 spthiel

Also happens on mac, if not connected to internet. Steps to reproduce:

1- listen to port 3000 on localhost:

const http = require('http');
http.createServer((request, res) => {
  res.write('Working!');
  res.end();
}).listen(3000);

2- disconnect from wifi (works if connected) 3- try below:

// works:
await axios.get("http://localhost:3000")

// fails: Uncaught AxiosError: getaddrinfo ENOTFOUND test.localhost
await axios.get("http://test.localhost:3000")

info:

> process.versions
{
  node: '20.3.1',
  acorn: '8.8.2',
  ada: '2.5.0',
  ares: '1.19.1',
  base64: '0.5.0',
  brotli: '1.0.9',
  cjs_module_lexer: '1.2.2',
  cldr: '43.1',
  icu: '73.2',
  llhttp: '8.1.1',
  modules: '115',
  napi: '9',
  nghttp2: '1.54.0',
  openssl: '3.1.1',
  simdutf: '3.2.12',
  tz: '2023c',
  undici: '5.22.1',
  unicode: '15.0',
  uv: '1.45.0',
  uvwasi: '0.0.18',
  v8: '11.3.244.8-node.9',
  zlib: '1.2.11'
}

ozgeneral avatar May 20 '24 12:05 ozgeneral

I suggest removing windows from bug title, as this seems to be non-OS specific issue.

one workaround could be to add subdomain.localhost to hosts in OS, but this seems like a bug to me, given curl and browsers are able to route localhost subdomains

ozgeneral avatar May 20 '24 12:05 ozgeneral

Interesting observation, it seems like OS can't resolve the dns with subdomains if not connected to internet:

$ # connected to internet

$ dscacheutil -q host -a name localhost  
name: localhost
ipv6_address: ::1

name: localhost
ip_address: 127.0.0.1

$ dscacheutil -q host -a name test.localhost
name: test.localhost
ipv6_address: ::1

name: test.localhost
ip_address: 127.0.0.1

$ # not connected to internet

$ dscacheutil -q host -a name localhost     
name: localhost
ipv6_address: ::1

name: localhost
ip_address: 127.0.0.1

$ dscacheutil -q host -a name test.localhost
$ 

ozgeneral avatar May 20 '24 12:05 ozgeneral

Interesting observation, it seems like OS can't resolve the dns with subdomains if not connected to internet:

$ # connected to internet

$ dscacheutil -q host -a name localhost  
name: localhost
ipv6_address: ::1

name: localhost
ip_address: 127.0.0.1

$ dscacheutil -q host -a name test.localhost
name: test.localhost
ipv6_address: ::1

name: test.localhost
ip_address: 127.0.0.1

$ # not connected to internet

$ dscacheutil -q host -a name localhost     
name: localhost
ipv6_address: ::1

name: localhost
ip_address: 127.0.0.1

$ dscacheutil -q host -a name test.localhost
$ 

ozgeneral avatar May 20 '24 12:05 ozgeneral

here is a somewhat hacky fix, I think this should be handled in both OS and nodejs level, nevertheless:

var axios = require("axios")
var http_adapter = require('axios/lib/adapters/http')
var settle = require('axios/lib/core/settle')

// also works
await axios.get("http://testing.localhost:3000", {
    adapter: (config) => {
        const regex = /(?<prefix>^https?:\/\/)(?<subdomain>.*\.)(?<suffix>localhost.*)/gi
        const subdomain_matches = Array.from(config.url.matchAll(regex))
        if (subdomain_matches.length > 0) {
            config.headers["Host"] = config.url
            config.url = `${subdomain_matches[0].groups.prefix}${subdomain_matches[0].groups.suffix}`
        }

        return new Promise((resolve, reject) => {
            http_adapter(config).then(response => {settle(resolve, reject, response)}).catch(reject)
        })
    }
})

although there is a solution with adapter, I suggest adding the localhost juggling inside nodejs package with proper testing so it can handle all users' request more robust out of box.

ozgeneral avatar May 20 '24 13:05 ozgeneral

Interesting observation, it seems like OS can't resolve the dns with subdomains if not connected to internet:

$ # connected to internet

$ dscacheutil -q host -a name localhost  
name: localhost
ipv6_address: ::1

name: localhost
ip_address: 127.0.0.1

$ dscacheutil -q host -a name test.localhost
name: test.localhost
ipv6_address: ::1

name: test.localhost
ip_address: 127.0.0.1

$ # not connected to internet

$ dscacheutil -q host -a name localhost     
name: localhost
ipv6_address: ::1

name: localhost
ip_address: 127.0.0.1

$ dscacheutil -q host -a name test.localhost
$ 

This issue persists on my mac (m1 running Sonoma 14.6.1) regardless of an internet connection. Running the same commands, dscacheutil is able to resolve the DNS with subdomains with or without an internet connection. The issue also happens with Python using requests but cURL and Postman always work.

An alternative is to rely on localtest.me for routing to subdomains of localhost but this will not work offline + you're now relying on an external system.

rdeavila94 avatar Sep 05 '24 20:09 rdeavila94