-
Notifications
You must be signed in to change notification settings - Fork 26
Description
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
- Have a hostname that resolves to multiple IPs (e.g.,
localhost>127.0.0.1+::127) - 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:
- Starts dial goroutines for ALL IPs in parallel
- Uses a WaitGroup that blocks until ALL dials complete
- Only then returns the successful connections
fastdialer/fastdialer/utils/dialwrap.go
Lines 221 to 250 in 8ce6b4b
| 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).getConnLook at those critical entries:
- 10.02s -
DialWrap.dialAllParallelwaiting on channel for first connection results - 10.02s -
net.(*netFD).connect.func2- waiting for TCP connection to establish - 10s -
DialWrap.DialContextwaiting 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
/etc/hostscontains both IPv4 and IPv6 entries forlocalhost:127.0.0.1 localhost ::127 localhost- Server listening only on
127.0.0.1:1231(Nuclei disconnecting early when pinging to Interactsh URL (HTTP) nuclei#6631) - fastdialer version: v0.4.x (via nuclei v3.6.0)
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.