Skip to content

sensitivity to order of connection errors #5092

@konrad-erli

Description

@konrad-erli

Bug Description

When making requests that attempt connection to multiple ip addresses (like a ipv4 and a ipv6), then rejection is quite volatile:

  • when last attempted socket ended with timeout then it always throws ConnectTimeoutError, hiding reasons of previous unsuccessfull connection attempts
  • otherwise a AggregateError is throws with raw errors from node:net inside

Reproducible By

setup firewall

sudo iptables -A INPUT -p tcp --dport 3112 -j DROP
sudo ip6tables -A INPUT -p tcp --dport 3112 -j REJECT --reject-with tcp-reset
sudo iptables -A INPUT -p tcp --dport 3113 -j REJECT --reject-with tcp-reset
sudo ip6tables -A INPUT -p tcp --dport 3113 -j DROP

setup address that resolves to localhost on both families: write to /etc/hosts:

::1       example.com
127.0.0.1 example.com

run in node

await require('undici').request('http://foofoo.in:3112')
await require('undici').request('http://foofoo.in:3113')

Expected Behavior

I would expect both requests to reject similar payload

Logs & Screenshots

> await require('undici').request('http://example.com:3112')
Uncaught:
ConnectTimeoutError: Connect Timeout Error (attempted addresses: ::1:3112, 127.0.0.1:3112, timeout: 10000ms)
    at onConnectTimeout (/home/konrad/erli/services/product/node_modules/undici/lib/core/util.js:894:19)
    at Immediate._onImmediate (/home/konrad/erli/services/product/node_modules/undici/lib/core/util.js:863:11)
    at process.processImmediate (node:internal/timers:504:21)
    at process.topLevelDomainCallback (node:domain:161:15)
    at process.callbackTrampoline (node:internal/async_hooks:128:24) {
  code: 'UND_ERR_CONNECT_TIMEOUT'
}


> await require('undici').request('http://example.com:3113')
Uncaught AggregateError [ETIMEDOUT]: 
    at internalConnectMultiple (node:net:1134:18)
    at afterConnectMultiple (node:net:1715:7)
    at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
  code: 'ETIMEDOUT',
  [errors]: [
    Error: connect ETIMEDOUT ::1:3113
        at createConnectionError (node:net:1678:14)
        at Timeout.internalConnectMultipleTimeout (node:net:1737:38)
        at listOnTimeout (node:internal/timers:607:11)
        at process.processTimers (node:internal/timers:541:7) {
      errno: -110,
      code: 'ETIMEDOUT',
      syscall: 'connect',
      address: '::1',
      port: 3113
    },
    Error: connect ECONNREFUSED 127.0.0.1:3113
        at createConnectionError (node:net:1678:14)
        at afterConnectMultiple (node:net:1708:16)
        at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
      errno: -111,
      code: 'ECONNREFUSED',
      syscall: 'connect',
      address: '127.0.0.1',
      port: 3113
    }
  ]
}

Environment

Ubuntu 24.04, Node 24.12, undici 8.1.0 (and 7.18)

Additional context

I had retrying/error handling logic for UND_ERR_CONNECT_TIMEOUT. When ipv6 is enabled but not functional, the errors that would normally be UND_ERR_CONNECT_TIMEOUT changed into AggregateError with ETIMEDOUT and ENETUNREACH, and I didn't handle them properly (not to mention that they are harder to handle).

I see a lot of effort was put into repacking those netive errors errors from lib/core/errors.js, it would be great if one could rely on handling just those errors, to have no edge cases or have a definite guide that covers them.

Perhaps one of following proposals should be considered:

  • ENETUNREACH should be suppressed when other errors exist, as ENETUNREACH is usually of lesser importance… It would protect those migrating from ipv4 to dual stack servers from unexpected error format change.
  • some heuristic that packs those errors together but still exposes the details
  • it should be documented that undici can reject with AggregateError, but undici should replace ETIMEDOUT with UND_ERR_CONNECT_TIMEOUT inside AggregateError

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions