Skip to content

dialAllParallel waits for all dial attempts to complete, causing >10s delays when one IP times out #511

@dwisiswant0

Description

@dwisiswant0

Description

When dialing a hostname that resolves to multiple IP addresses (e.g., both IPv4 & IPv6), dialAllParallel() waits for all dial attempts to complete before returning results. This causes significant delays when one IP address has no listener and times out, even if another IP succeeds immediately.

Steps to Reproduce

  1. Have a hostname that resolves to multiple IPs (e.g., localhost > 127.0.0.1 + ::127)
  2. Replicate these: Nuclei disconnecting early when pinging to Interactsh URL (HTTP) nuclei#6631

Expected: Connection succeeds immediately (~ms) via the working IPv4 address
Actual: Connection takes ~10s (DialerTimeout) because it waits for the IPv6 dial to timeout

Root Cause

In fastdialer/utils/dialwrap.go, the dialAllParallel() function:

  1. Starts dial goroutines for ALL IPs in parallel
  2. Uses a WaitGroup that blocks until ALL dials complete
  3. Only then returns the successful connections

go func() {
defer close(rec)
defer wg.Wait()
for _, ip := range d.ips {
wg.Add(1)
go func(ipx net.IP) {
defer wg.Done()
select {
case <-ctx.Done():
rec <- &dialResult{error: errkit.Append(ErrInflightCancel, ctx.Err())}
default:
c, err := d.dialer.DialContext(ctx, d.network, net.JoinHostPort(ipx.String(), d.port))
rec <- &dialResult{Conn: c, error: err, expiry: time.Now().Add(ExpireConnAfter)}
}
}(ip)
}
}()
conns := []*dialResult{}
errs := []*dialResult{}
for result := range rec {
if result.Conn != nil {
conns = append(conns, result)
} else {
if !errkit.Is(result.error, ErrInflightCancel) {
errs = append(errs, result)
}
}
}

Note

L223: Waits for ALL dial attempts
L242: Blocks until channel is closed (after wg.Wait)

Evidence from block profile:

From a nuclei scan that took 25 seconds instead of <1 second:

$ go tool pprof -traces block.profile 2>&1 | head -150
Main binary filename not available.
Type: delay
-----------+-------------------------------------------------------
   12.03us   runtime.chanrecv1
             net.(*netFD).connect.func1
             net.(*netFD).connect
             net.(*netFD).dial
             net.socket
             net.internetSocket
             net.(*sysDialer).doDialTCPProto
             net.(*sysDialer).doDialTCP
             net.(*sysDialer).dialTCP
             net.(*sysDialer).dialSingle
             net.(*sysDialer).dialSerial
             net.(*sysDialer).dialParallel
             net.(*Dialer).DialContext
             github.com/projectdiscovery/fastdialer/fastdialer/utils.(*DialWrap).dialAllParallel.func1.1
-----------+-------------------------------------------------------
   22.17ms   runtime.selectgo
             github.com/projectdiscovery/fastdialer/fastdialer/utils.(*DialWrap).DialContext
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).dialIPS
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).dial
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).DialTLSWithConfig
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).DialTLS
             github.com/projectdiscovery/retryablehttp-go.DefaultReusePooledTransport.func2
             net/http.(*Transport).customDialTLS
             net/http.(*Transport).dialConn
             net/http.(*Transport).dialConnFor
             net/http.(*Transport).startDialConnForLocked.func1
-----------+-------------------------------------------------------
   25.02ms   runtime.selectgo
             crypto/tls.(*Conn).handshakeContext.func2
-----------+-------------------------------------------------------
    11.67s   runtime.chanrecv1
             runtime.(*wakeableSleep).sleep
             runtime.traceStartReadCPU.func1
-----------+-------------------------------------------------------
  104.90us   runtime.selectgo
             net/http.(*persistConn).readLoop
-----------+-------------------------------------------------------
    10.02s   runtime.chanrecv2
             github.com/projectdiscovery/fastdialer/fastdialer/utils.(*DialWrap).dialAllParallel
             github.com/projectdiscovery/fastdialer/fastdialer/utils.(*DialWrap).doFirstConnection.func1
-----------+-------------------------------------------------------
    10.02s   sync.(*WaitGroup).Wait
             github.com/projectdiscovery/fastdialer/fastdialer/utils.(*DialWrap).dialAllParallel.func1
-----------+-------------------------------------------------------
    6.27us   runtime.chansend1
             runtime.sigenable
             os/signal.signal_enable
             os/signal.enableSignal
             os/signal.Notify.func1
             os/signal.Notify
             main.main
-----------+-------------------------------------------------------
   40.26us   runtime.selectgo
             net/http.(*Transport).getConn
             net/http.(*Transport).roundTrip
             net/http.(*Transport).RoundTrip
             net/http/httputil.DumpRequestOut
             github.com/projectdiscovery/retryablehttp-go.(*Request).Dump
             github.com/projectdiscovery/nuclei/v3/pkg/protocols/http.dump
             github.com/projectdiscovery/nuclei/v3/pkg/protocols/http.(*Request).executeRequest
             github.com/projectdiscovery/nuclei/v3/pkg/protocols/http.(*Request).ExecuteWithResults.func1
             github.com/projectdiscovery/nuclei/v3/pkg/protocols/http.(*Request).ExecuteWithResults
             github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/generic.(*Generic).ExecuteWithResults
             github.com/projectdiscovery/nuclei/v3/pkg/tmplexec.(*TemplateExecuter).Execute
             github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateOnInput
             github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateWithTargets.func2.1
             github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateWithTargets.func2
-----------+-------------------------------------------------------
       10s   runtime.selectgo
             github.com/projectdiscovery/fastdialer/fastdialer/utils.(*DialWrap).DialContext
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).dialIPS
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).dial
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).Dial
             net/http.(*Transport).dial
             net/http.(*Transport).dialConn
             net/http.(*Transport).dialConnFor
             net/http.(*Transport).startDialConnForLocked.func1
-----------+-------------------------------------------------------
    10.02s   runtime.selectgo
             net.(*netFD).connect.func2
-----------+-------------------------------------------------------
  283.84us   runtime.selectgo
             github.com/projectdiscovery/fastdialer/fastdialer/utils.(*DialWrap).dialParallel
             github.com/projectdiscovery/fastdialer/fastdialer/utils.(*DialWrap).dial
             github.com/projectdiscovery/fastdialer/fastdialer/utils.(*DialWrap).DialContext
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).dialIPS
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).dial
             github.com/projectdiscovery/fastdialer/fastdialer.(*Dialer).Dial
             net/http.(*Transport).dial
             net/http.(*Transport).dialConn
             net/http.(*Transport).dialConnFor
             net/http.(*Transport).startDialConnForLocked.func1
-----------+-------------------------------------------------------
   25.10ms   runtime.selectgo
             github.com/projectdiscovery/fastdialer/fastdialer.closeAfterTimeout.func1
-----------+-------------------------------------------------------
   18.04ms   sync.(*WaitGroup).Wait
             github.com/projectdiscovery/utils/sync.(*AdaptiveWaitGroup).Wait
             github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader.(*Store).LoadTemplatesWithTags
             github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader.(*Store).LoadTemplates
             github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader.(*Store).Load
             github.com/projectdiscovery/nuclei/v3/internal/runner.(*Runner).RunEnumeration
             main.main
-----------+-------------------------------------------------------
       20s   sync.(*WaitGroup).Wait
             github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateWithTargets
             github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateSpray.func1
-----------+-------------------------------------------------------
    30.02s   runtime.selectgo
             net/http.(*persistConn).writeLoop
-----------+-------------------------------------------------------
    8.13us   sync.(*Mutex).Lock
             net/http.(*persistConn).readLoop
-----------+-------------------------------------------------------
   45.31us   runtime.selectgo
             net/http.(*Transport).getConn
             net/http.(*Transport).roundTrip
             net/http.(*Transport).RoundTrip
             net/http/httputil.DumpRequestOut
             github.com/projectdiscovery/retryablehttp-go.(*Request).Dump
             github.com/projectdiscovery/nuclei/v3/pkg/protocols/http.dump
             github.com/projectdiscovery/nuclei/v3/pkg/protocols/http.(*Request).executeRequest
             github.com/projectdiscovery/nuclei/v3/pkg/protocols/http.(*Request).ExecuteWithResults.func1
             github.com/projectdiscovery/nuclei/v3/pkg/protocols/http.(*Request).ExecuteWithResults
             github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/generic.(*Generic).ExecuteWithResults
             github.com/projectdiscovery/nuclei/v3/pkg/tmplexec.(*TemplateExecuter).Execute
             github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateOnInput
             github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateWithTargets.func2.1
             github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateWithTargets.func2
-----------+-------------------------------------------------------
  811.33us   runtime.chanrecv2
             github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateWithTargets.func2
-----------+-------------------------------------------------------
   96.44ms   runtime.selectgo
             net/http.(*persistConn).roundTrip
             net/http.(*Transport).roundTrip
             net/http.(*Transport).RoundTrip
             net/http.send
             net/http.(*Client).send
             net/http.(*Client).do
             net/http.(*Client).Do
             github.com/projectdiscovery/retryablehttp-go.(*Client).Do
             github.com/projectdiscovery/interactsh/pkg/client.(*Client).getInteractions
             github.com/projectdiscovery/interactsh/pkg/client.(*Client).StartPolling.func1
-----------+-------------------------------------------------------
       10s   runtime.selectgo
             net/http.(*Transport).getConn

Look at those critical entries:

  • 10.02s - DialWrap.dialAllParallel waiting on channel for first connection results
  • 10.02s - net.(*netFD).connect.func2 - waiting for TCP connection to establish
  • 10s - DialWrap.DialContext waiting in selectgo (for first connection completion)
  • 30.02s - net/http.(*persistConn).writeLoop - waiting to write HTTP data

The 10-second blocks are the DialerTimeout, one IP (IPv6 ::127) had nothing listening, so it timed out.

Environment

Impact

This bug affects any scenario where:

  • A hostname resolves to multiple IPs.
  • Some IPs are unreachable or filtered.
  • Users experience 10+ second delays per request instead of immediate connections.

Commonly affected: localhost, dual-stack hosts, hosts with stale DNS records pointing to dead IPs.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions