Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not using local DNS in /etc/resolver/* without internet #29

Closed
IlyaSemenov opened this issue May 7, 2020 · 30 comments
Closed

Not using local DNS in /etc/resolver/* without internet #29

IlyaSemenov opened this issue May 7, 2020 · 30 comments
Labels
help wanted Extra attention is needed

Comments

@IlyaSemenov
Copy link

IlyaSemenov commented May 7, 2020

When there is no internet connectivity (either interface down, or when there are problems at uplink), cacheable-lookup stops resolving xxx.test records from /etc/resolver/test.

With internet:

> require('got').get('http://django.test:8000/admin/').then(res => console.log(res.body))
Promise { <pending> }
> <!DOCTYPE html>

without internet:

> require('got').get('http://django.test:8000/admin/').then(res => console.log(res.body))
Promise { <pending> }
>
> (node:13938) UnhandledPromiseRejectionWarning: RequestError: ENOTFOUND django.test
...
    at CacheableLookup.lookupAsync (/Users/semenov/test/node_modules/cacheable-lookup/source/index.js:157:18)

for comparison, modules that don't use cacheable-lookup work just fine without internet:

> require('axios').get('http://django.test:8000/admin/').then(res => console.log(res.data))
Promise { <pending> }
> <!DOCTYPE html>

or:

> require('got').get('http://django.test:8000/admin/', { dnsCache: false }).then(res => console.log(res.body))
Promise { <pending> }
> <!DOCTYPE html>

my DNS setup is:

❯ cat /etc/resolver/test
nameserver 127.0.0.1

❯ cat /usr/local/etc/dnsmasq.d/development.conf
address=/.test/127.0.0.1

This is Mac OS 10.15.4.

@szmarczak
Copy link
Owner

Are you using at least v4.2.0? Versions before that do not fall back to dns.lookup(...), so other DNS solutions than the hosts file don't work there.

@szmarczak
Copy link
Owner

When developing, I recommend setting options.fallbackTtl to 3600 (1 hour).

@IlyaSemenov
Copy link
Author

My bad, I forgot to mention. This is 4.2.3, installed a couple days ago (as a got dependency).

@IlyaSemenov
Copy link
Author

When developing, I recommend setting options.fallbackTtl to 3600 (1 hour).

I tried it, and it (expectedly) didn't work:

> CacheableLookup=require('cacheable-lookup'); require('got').get('http://django.test:8000/admin/', { dnsCache: new CacheableLookup({ fallbackTtl: 3600 }) }).then(res => console.log(res.body))
Promise { <pending> }
>
> (node:26314) UnhandledPromiseRejectionWarning: RequestError: ENOTFOUND django.test

I'm not sure what TTL has to do with this. After all, the system might have never seen internet since boot, e.g. if I'm on a train in Cambodia. All other lookup methods correctly resolve django.test and cacheable-lookup fails to do so.

@szmarczak
Copy link
Owner

What's the result of npm ls cacheable-lookup?

@IlyaSemenov
Copy link
Author

❯ npm ls cacheable-lookup
test@0.1.0 /Users/semenov/work/test
└─┬ got@11.1.0
  └── cacheable-lookup@4.2.3

@szmarczak
Copy link
Owner

Actually I can reproduce this, thanks for reporting!

@szmarczak szmarczak added the bug Something isn't working label May 7, 2020
@szmarczak
Copy link
Owner

This bug is really interesting. It should never happen.

@szmarczak
Copy link
Owner

Actually... um this is not a bug? I was following the Node.js docs:

dns.ADDRCONFIG: Returned address types are determined by the types of addresses supported by the current system. For example, IPv4 addresses are only returned if the current system has at least one IPv4 address configured. Loopback addresses are not considered.

I'll open a Node.js issue about that.

@IlyaSemenov
Copy link
Author

I was actually experiencing the same problem when my system had an IP address assigned by the router's DHCP server (192.168.0.x) but the router uplink was broken. So I don't think what you referred to was the only reason.

@szmarczak
Copy link
Owner

I don't know exactly how os.networkInterfaces() works under the hood. I just know that I can use it to mimic the ADDRCONFIG flag. But I think I know how to solve this problem. It may relate to DNS queries only, so local definitions won't be affected by this (I think).

@szmarczak
Copy link
Owner

I can confirm that on Windows it works as expected (dns.lookup(...) throws).

@szmarczak szmarczak added external and removed bug Something isn't working labels May 7, 2020
@szmarczak
Copy link
Owner

Relevant Node.js issue: nodejs/node#33279

@szmarczak
Copy link
Owner

@sindresorhus I think it would be a good choice to remove the HostsResolver and increase fallbackTtl to 60 (1 minute).

@szmarczak
Copy link
Owner

I was actually experiencing the same problem when my system had an IP address assigned by the router's DHCP server (192.168.0.x) but the router uplink was broken.

That must've been a new interface. I guess the Node.js app had started before, right?

@IlyaSemenov
Copy link
Author

No, if that were the case and if it could have been fixed by simply restarting the node app I wouldn't be bothering opening this ticket.

This is what I've repeated just now:

  1. I ran node and require('got').get('http://django.test:8000/admin/').then(res => console.log(res.body)); , it worked fine
  2. I unplugged ISP cable from the router (my laptop wifi<->router connection stayed intact, ifconfig output didn't change).
  3. I repeated step (1) running fresh node, it crashed with: connect ECONNREFUSED 192.168.0.1:8000 (????) - very odd, why would it be trying connecting the router at port 8000?
  4. I plugged ISP cable back to the router
  5. I repeated step (1) running fresh node, it still crashed
  6. I repeated step (1) running fresh node, it worked fine

ifconfig output never changed during the process, I tested with diff:

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
	options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
	inet 127.0.0.1 netmask 0xff000000 
	inet6 ::1 prefixlen 128 
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 
	nd6 options=201<PERFORMNUD,DAD>
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
stf0: flags=0<> mtu 1280
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
	options=400<CHANNEL_IO>
	ether a4:5e:60:d5:b5:b5 
	inet6 fe80::8b7:9dfc:931:ee60%en0 prefixlen 64 secured scopeid 0x4 
	inet 192.168.0.18 netmask 0xffffff00 broadcast 192.168.0.255
	nd6 options=201<PERFORMNUD,DAD>
	media: autoselect
	status: active
p2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304
	options=400<CHANNEL_IO>
	ether 06:5e:60:d5:b5:b5 
	media: autoselect
	status: inactive
awdl0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1484
	options=400<CHANNEL_IO>
	ether 8a:5d:12:f3:91:21 
	inet6 fe80::885d:12ff:fef3:9121%awdl0 prefixlen 64 scopeid 0x6 
	nd6 options=201<PERFORMNUD,DAD>
	media: autoselect
	status: active
llw0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
	options=400<CHANNEL_IO>
	ether 8a:5d:12:f3:91:21 
	inet6 fe80::885d:12ff:fef3:9121%llw0 prefixlen 64 scopeid 0x7 
	nd6 options=201<PERFORMNUD,DAD>
	media: autoselect
	status: active
en1: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
	options=460<TSO4,TSO6,CHANNEL_IO>
	ether 82:13:03:d1:ff:80 
	media: autoselect <full-duplex>
	status: inactive
en2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
	options=460<TSO4,TSO6,CHANNEL_IO>
	ether 82:13:03:d1:ff:81 
	media: autoselect <full-duplex>
	status: inactive
bridge0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
	options=63<RXCSUM,TXCSUM,TSO4,TSO6>
	ether 82:13:03:d1:ff:80 
	Configuration:
		id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0
		maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200
		root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0
		ipfilter disabled flags 0x0
	member: en1 flags=3<LEARNING,DISCOVER>
	        ifmaxaddr 0 port 8 priority 0 path cost 0
	member: en2 flags=3<LEARNING,DISCOVER>
	        ifmaxaddr 0 port 9 priority 0 path cost 0
	nd6 options=201<PERFORMNUD,DAD>
	media: <unknown type>
	status: inactive
utun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1380
	inet6 fe80::50b2:9471:dbc5:ac64%utun0 prefixlen 64 scopeid 0xb 
	nd6 options=201<PERFORMNUD,DAD>
utun1: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 2000
	inet6 fe80::9acb:ae4c:2b68:e8ce%utun1 prefixlen 64 scopeid 0xc 
	nd6 options=201<PERFORMNUD,DAD>

ping django.test, curl http://django.test:8000/admin/ and axios all worked fine regardless of the connectivity. (as expected)

@szmarczak
Copy link
Owner

I tried the same but with my Ethernet cable directly plugged into my computer. Will try with the router now.

@szmarczak
Copy link
Owner

I tried your steps but was playing with the hosts file instead, because my distro doesn't allow me to alter resolv.conf. I wasn't able to reproduce. I'll run ubuntu in docker and see how's things.

@szmarczak
Copy link
Owner

Ok, so I created an Ubuntu instance, set up dnsmasq, installed latest Node.js 12 and ran require('got').get('http://asdf.test').then(response => console.log(response.body.length)) and everything worked. My PC was disconnected from Internet. Note that *.test points to my nginx server.

root@972c5d938765:/bug# npm ls
bug@1.0.0 /bug
`-- got@11.1.1
  +-- @sindresorhus/is@2.1.1
  +-- @szmarczak/http-timer@4.0.5
  | `-- defer-to-connect@2.0.0
  +-- @types/cacheable-request@6.0.1
  | +-- @types/http-cache-semantics@4.0.0
  | +-- @types/keyv@3.1.1
  | | `-- @types/node@13.13.5 deduped
  | +-- @types/node@13.13.5
  | `-- @types/responselike@1.0.0 deduped
  +-- @types/responselike@1.0.0
  | `-- @types/node@13.13.5 deduped
  +-- cacheable-lookup@4.2.3
  +-- cacheable-request@7.0.1
  | +-- clone-response@1.0.2
  | | `-- mimic-response@1.0.1
  | +-- get-stream@5.1.0 deduped
  | +-- http-cache-semantics@4.1.0
  | +-- keyv@4.0.1
  | | `-- json-buffer@3.0.1
  | +-- lowercase-keys@2.0.0 deduped
  | +-- normalize-url@4.5.0
  | `-- responselike@2.0.0 deduped
  +-- decompress-response@5.0.0
  | `-- mimic-response@2.1.0
  +-- get-stream@5.1.0
  | `-- pump@3.0.0
  |   +-- end-of-stream@1.4.4
  |   | `-- once@1.4.0 deduped
  |   `-- once@1.4.0
  |     `-- wrappy@1.0.2
  +-- http2-wrapper@1.0.0-beta.4.5
  | +-- quick-lru@5.1.0
  | `-- resolve-alpn@1.0.0
  +-- lowercase-keys@2.0.0
  +-- p-cancelable@2.0.0
  `-- responselike@2.0.0
    `-- lowercase-keys@2.0.0 deduped

@szmarczak
Copy link
Owner

connect ECONNREFUSED 192.168.0.1:8000

What's the stack trace?

@szmarczak szmarczak added help wanted Extra attention is needed and removed external labels May 7, 2020
@szmarczak
Copy link
Owner

Released got@11.1.2 which has disabled DNS cache by default.

@IlyaSemenov
Copy link
Author

What's the stack trace?

with Internet connectivity:

test ❯ node
Welcome to Node.js v12.8.0.
Type ".help" for more information.
> require('got').get('http://asdf.test:12345').then(response => console.log(response.body.length))
Promise { <pending> }
> (node:28301) UnhandledPromiseRejectionWarning: RequestError: connect ECONNREFUSED 127.0.0.1:12345
    at ClientRequest.<anonymous> (/Users/semenov/work/test/node_modules/got/dist/source/core/index.js:786:25)
    at Object.onceWrapper (events.js:291:20)
    at ClientRequest.emit (events.js:208:15)
    at ClientRequest.EventEmitter.emit (domain.js:476:20)
    at ClientRequest.origin.emit (/Users/semenov/work/test/node_modules/@szmarczak/http-timer/dist/source/index.js:39:20)
    at Socket.socketErrorListener (_http_client.js:399:9)
    at Socket.emit (events.js:203:13)
    at Socket.EventEmitter.emit (domain.js:476:20)
    at emitErrorNT (internal/streams/destroy.js:91:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1056:14)
(node:28301) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:28301) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

without internet (unplugged ISP cable from the router, ifconfig on the laptop didn't change):

test ❯ node
Welcome to Node.js v12.8.0.
Type ".help" for more information.
> require('got').get('http://asdf.test:12345').then(response => console.log(response.body.length))
Promise { <pending> }
> (node:28187) UnhandledPromiseRejectionWarning: RequestError: connect ECONNREFUSED 192.168.0.1:12345
    at ClientRequest.<anonymous> (/Users/semenov/work/test/node_modules/got/dist/source/core/index.js:786:25)
    at Object.onceWrapper (events.js:291:20)
    at ClientRequest.emit (events.js:208:15)
    at ClientRequest.EventEmitter.emit (domain.js:476:20)
    at ClientRequest.origin.emit (/Users/semenov/work/test/node_modules/@szmarczak/http-timer/dist/source/index.js:39:20)
    at Socket.socketErrorListener (_http_client.js:399:9)
    at Socket.emit (events.js:203:13)
    at Socket.EventEmitter.emit (domain.js:476:20)
    at emitErrorNT (internal/streams/destroy.js:91:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1056:14)
(node:28187) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:28187) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

you can see how the IP changes.

This could be perhaps something Mac OS-related.

@szmarczak
Copy link
Owner

Can you try #31 (fixes branch) and see if it fixes the issue?

@szmarczak
Copy link
Owner

I managed to make all the current tests pass there. But it's missing some new tests anyway. Can you try it?

@szmarczak
Copy link
Owner

I've released cacheable-lookup@5.0.0. Please try the following:

npm install got@11.1.2 cacheable-lookup@5.0.0
const got = require('got');
const CacheableLookup = require('cacheable-lookup');

const dnsCache = new CacheableLookup();

got.get('http://django.test:8000/admin/', {dnsCache}).then(res => console.log(res.body));

Please let me know if it's fixed or not.

@IlyaSemenov
Copy link
Author

npm install got@11.1.2 cacheable-lookup@5.0.0

This leads to:

test ❯ node
Welcome to Node.js v12.8.0.
Type ".help" for more information.
> const got = require('got');

undefined
>
> const CacheableLookup = require('cacheable-lookup');
undefined
>
> const dnsCache = new CacheableLookup();
undefined
>
> got.get('http://django.test:8000/admin/', {dnsCache}).then(res => console.log(res.body));
Promise { <pending> }
> (node:43447) UnhandledPromiseRejectionWarning: TypeError: Parameter `dnsCache` must be a CacheableLookup instance or a boolean, got Object
...
> dnsCache
CacheableLookup {
  maxTtl: Infinity,
  errorTtl: 0.15,
...

I figured it's because got lists "cacheable-lookup": "^4.3.0" as the dependency. Anyhow after I fixed the dependency/deleted extra modules from got/node_modules, that's what I get with wifi disabled:

❯ node
Welcome to Node.js v12.8.0.
Type ".help" for more information.
> const got = require('got');
undefined
> const CacheableLookup = require('cacheable-lookup');
undefined
> const dnsCache = new CacheableLookup();
undefined
> got.get('http://django.test:8000/admin/', {dnsCache}).then(res => console.log(res.body));
Promise { <pending> }
>
> (node:46652) UnhandledPromiseRejectionWarning: RequestError: cacheableLookup ENOTFOUND django.test
    at ClientRequest.<anonymous> (/private/tmp/test/node_modules/got/dist/source/core/index.js:767:25)
    at Object.onceWrapper (events.js:291:20)
    at ClientRequest.emit (events.js:208:15)
    at ClientRequest.EventEmitter.emit (domain.js:476:20)
    at ClientRequest.origin.emit (/private/tmp/test/node_modules/@szmarczak/http-timer/dist/source/index.js:39:20)
    at Socket.socketErrorListener (_http_client.js:399:9)
    at Socket.emit (events.js:203:13)
    at Socket.EventEmitter.emit (domain.js:476:20)
    at emitErrorNT (internal/streams/destroy.js:91:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)
    at CacheableLookup.lookupAsync (/private/tmp/test/node_modules/cacheable-lookup/source/index.js:183:18)
(node:46652) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:46652) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

and for comparison:

> got.get('http://django.test:8000/admin/').then(res => console.log(res.body.length));
Promise { <pending> }
> 1915
/private/tmp/test
├─┬ axios@0.19.2
│ └─┬ follow-redirects@1.5.10
│   └─┬ debug@3.1.0
│     └── ms@2.0.0
├── cacheable-lookup@5.0.0
└─┬ got@11.1.2
  ├── @sindresorhus/is@2.1.1
  ├─┬ @szmarczak/http-timer@4.0.5
  │ └── defer-to-connect@2.0.0
  ├─┬ @types/cacheable-request@6.0.1
  │ ├── @types/http-cache-semantics@4.0.0
  │ ├─┬ @types/keyv@3.1.1
  │ │ └── @types/node@13.13.5 deduped
  │ ├── @types/node@13.13.5
  │ └── @types/responselike@1.0.0 deduped
  ├─┬ @types/responselike@1.0.0
  │ └── @types/node@13.13.5 deduped
  ├── UNMET DEPENDENCY cacheable-lookup@4.3.0
  ├─┬ cacheable-request@7.0.1
  │ ├─┬ clone-response@1.0.2
  │ │ └── mimic-response@1.0.1
  │ ├── get-stream@5.1.0 deduped
  │ ├── http-cache-semantics@4.1.0
  │ ├─┬ keyv@4.0.1
  │ │ └── json-buffer@3.0.1
  │ ├── lowercase-keys@2.0.0 deduped
  │ ├── normalize-url@4.5.0
  │ └── responselike@2.0.0 deduped
  ├─┬ decompress-response@5.0.0
  │ └── mimic-response@2.1.0 extraneous
  ├─┬ get-stream@5.1.0
  │ └─┬ pump@3.0.0
  │   ├─┬ end-of-stream@1.4.4
  │   │ └── once@1.4.0 deduped
  │   └─┬ once@1.4.0
  │     └── wrappy@1.0.2
  ├─┬ http2-wrapper@1.0.0-beta.4.5
  │ ├── quick-lru@5.1.0
  │ └── resolve-alpn@1.0.0
  ├── lowercase-keys@2.0.0
  ├── p-cancelable@2.0.0
  └─┬ responselike@2.0.0
    └── lowercase-keys@2.0.0 deduped

@szmarczak
Copy link
Owner

That's good news! I think the problem is that resolve4 and resolve6 finish before dns.lookup so it doesn't have the chance to get the entries from the OS.

@szmarczak
Copy link
Owner

Can you try again with cacheable-lookup@5.0.2? Thanks.

@szmarczak
Copy link
Owner

Closing due to lack of response. I cannot reproduce it anymore with the newest version.

@IlyaSemenov
Copy link
Author

I confirm that it works with cacheable-lookup@5.0.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants