Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
81fdbdf
deadline handshake
Mzack9999 Jun 25, 2026
62bf110
chore(deps): bump github.com/projectdiscovery/networkpolicy (#555)
dependabot[bot] Jun 29, 2026
bd0de71
chore(deps): bump golang.org/x/net from 0.48.0 to 0.55.0 (#556)
dependabot[bot] Jul 3, 2026
7636c07
chore(deps): bump github.com/projectdiscovery/networkpolicy (#557)
dependabot[bot] Jul 6, 2026
58ca67f
chore(deps): bump golang.org/x/crypto from 0.51.0 to 0.52.0 (#558)
dependabot[bot] Jul 10, 2026
5ebaf44
chore(deps): bump github.com/projectdiscovery/networkpolicy (#559)
dependabot[bot] Jul 27, 2026
a5edbbf
chore(deps): bump github.com/projectdiscovery/networkpolicy (#560)
dependabot[bot] Aug 3, 2026
40418de
chore(deps): bump github.com/projectdiscovery/blackrock (#561)
dependabot[bot] Aug 10, 2026
4a890db
chore(deps): bump github.com/projectdiscovery/networkpolicy (#562)
dependabot[bot] Aug 10, 2026
43e317c
chore(deps): bump github.com/projectdiscovery/networkpolicy (#563)
dependabot[bot] Aug 17, 2026
eff51d6
chore(deps): bump github.com/projectdiscovery/networkpolicy (#564)
dependabot[bot] Aug 24, 2026
aa6b351
chore(deps): bump github.com/projectdiscovery/hmap (#565)
dependabot[bot] Aug 31, 2026
14a4605
chore(deps): bump github.com/projectdiscovery/utils (#566)
dependabot[bot] Aug 31, 2026
31ac4f3
chore(deps): bump github.com/projectdiscovery/networkpolicy (#567)
dependabot[bot] Aug 31, 2026
19203fa
chore(deps): bump github.com/projectdiscovery/retryabledns (#568)
dependabot[bot] Aug 31, 2026
9aeb11f
chore(deps): bump github.com/projectdiscovery/networkpolicy (#570)
dependabot[bot] Sep 7, 2026
7b1da3d
chore(deps): bump github.com/projectdiscovery/utils (#569)
dependabot[bot] Sep 7, 2026
a6c0214
chore(deps): bump github.com/projectdiscovery/networkpolicy (#571)
dependabot[bot] Sep 14, 2026
6390c20
chore(deps): bump github.com/projectdiscovery/networkpolicy (#573)
dependabot[bot] Sep 21, 2026
37de254
chore(deps): bump github.com/projectdiscovery/utils (#574)
dependabot[bot] Sep 21, 2026
ffbc780
chore(deps): bump github.com/projectdiscovery/utils (#575)
dependabot[bot] Sep 28, 2026
7f2e264
chore(deps): bump github.com/projectdiscovery/networkpolicy (#576)
dependabot[bot] Sep 28, 2026
4429f81
chore(deps): bump github.com/projectdiscovery/networkpolicy (#577)
dependabot[bot] Oct 5, 2026
35047f1
chore(deps): bump github.com/projectdiscovery/utils (#582)
dependabot[bot] Oct 12, 2026
fb190cf
chore(deps): bump github.com/projectdiscovery/networkpolicy (#581)
dependabot[bot] Oct 12, 2026
0ecbc65
fix: return the first connection instead of dialing again
dogancanbakir Oct 12, 2026
37bcaf8
fix: lint with golangci-lint v2 and fix its findings
dogancanbakir Oct 12, 2026
55f14a3
Merge pull request #586 from projectdiscovery/fix/585-lint-go126
dogancanbakir Oct 13, 2026
7b23e14
Merge remote-tracking branch 'origin/main' into fix/583-first-dial-leak
dogancanbakir Oct 13, 2026
2e7cb56
Merge pull request #584 from projectdiscovery/fix/583-first-dial-leak
dogancanbakir Oct 13, 2026
c1cb39c
fix: honor NextProtos in impersonated TLS dials
dwisiswant0 Oct 14, 2026
6f5189f
bump zcrypto (#589)
Mzack9999 Oct 14, 2026
3367ef3
Merge pull request #592 from projectdiscovery/dwisiswant0/fix/honor-N…
Mzack9999 Oct 14, 2026
46f3e01
chore(deps): bump github.com/projectdiscovery/networkpolicy (#605)
dependabot[bot] Oct 19, 2026
95f1c1b
util: copy Certificates in AsZTLSConfig
dwisiswant0 Oct 19, 2026
0609158
fix: honor tls.Config in impersonated & ztls dials
dwisiswant0 Oct 19, 2026
00667ba
ja3: support TLS 1.3 fingerprints in ParseWithJa3
dwisiswant0 Oct 20, 2026
fb0f4bd
dialer: fall back to ztls only on TLS handshake errors
dwisiswant0 Oct 20, 2026
c238e68
report dial results to OnDialResult
dogancanbakir Oct 20, 2026
033a97d
Merge pull request #618 from projectdiscovery/dwisiswant0/ja3/support…
Mzack9999 Oct 20, 2026
c2aa648
Merge pull request #616 from projectdiscovery/dwisiswant0/util/copy-C…
Mzack9999 Oct 20, 2026
a6a5891
merge main
Mzack9999 Oct 20, 2026
aa69334
pair shares
Mzack9999 Oct 20, 2026
20a8eed
Merge pull request #617 from projectdiscovery/dwisiswant0/fix/honor-t…
Mzack9999 Oct 20, 2026
8b23544
fix fallback
Mzack9999 Oct 20, 2026
89f77d4
Merge pull request #619 from projectdiscovery/dwisiswant0/dialer/fall…
Mzack9999 Oct 20, 2026
f09ce86
align callback
Mzack9999 Oct 20, 2026
e8e50e8
Merge pull request #622 from projectdiscovery/feat/606-dial-result-ca…
Mzack9999 Oct 20, 2026
8dcda4d
chore(deps): bump github.com/klauspost/compress from 1.18.2 to 1.18.7…
dependabot[bot] Oct 21, 2026
f6c7386
bump zcrypto to fix darwin/arm64 nocgo build
dogancanbakir Oct 21, 2026
95eb731
dialer: don't leak the connection when ApplyPreset fails
dwisiswant0 Oct 20, 2026
64bf637
ja3: give each parsed spec its own extensions
dwisiswant0 Oct 20, 2026
ddc03ab
dialer: copy every extension of the identity for each dial
dwisiswant0 Oct 20, 2026
af6d907
dialer: honor MinVersion and MaxVersion in impersonated dials
dwisiswant0 Oct 20, 2026
3b81766
Merge pull request #626 from projectdiscovery/dwisiswant0/dialer/dont…
Mzack9999 Oct 21, 2026
4073fd1
Merge pull request #627 from projectdiscovery/dwisiswant0/dialer/copy…
Mzack9999 Oct 21, 2026
46323fa
Merge pull request #628 from projectdiscovery/dwisiswant0/dialer/hono…
Mzack9999 Oct 21, 2026
d8352b3
merge main
Mzack9999 Oct 22, 2026
57c5968
Merge pull request #625 from projectdiscovery/fix/zcrypto-darwin-arm64
Mzack9999 Oct 22, 2026
01ce3f7
merge main
dogancanbakir Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/actions/golangci-lint@v1
- uses: projectdiscovery/actions/golangci-lint/v2@v1

build:
name: Test Builds
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ For more details and documentation refer [GoDoc](https://pkg.go.dev/github.com/p

### ZTLS Fallback

fastdialer by default fallbacks to using zcrypto when there is an error in TLS handshake (insufficient security level etc ). This is done to support older TLS versions and ciphers. This can be disabled in fastdialer options or by using `DISABLE_ZTLS_FALLBACK=true` environment variable. when falling back to ztls, ChromeCiphers are used
When the TLS handshake of `DialTLS` or `DialTLSWithConfig` fails, fastdialer retries it once with zcrypto (ztls), to reach servers that `crypto/tls` rejects, such as servers with old TLS versions or legacy ciphers. The retry uses `ztls.ChromeCiphers` and the default ztls curves instead of the cipher suites and curve preferences of the config. It retries only when:

- the dial context isn't done, the handshake didn't time out, and the server's reply, if any, was TLS.
- the TLS config allows versions older than TLS 1.3.
- the TLS config uses nothing that zcrypto can't carry over: `InsecureSkipVerify` is `true`, and the config sets no `VerifyPeerCertificate` or `VerifyConnection` callback, no `GetClientCertificate`, and no Encrypted Client Hello settings. A `Certificates` chain is copied onto the retry. Otherwise the retry would verify the server differently, omit a certificate chosen by callback, or send the hidden server name in the clear.

Plain dials, including dials through a proxy, impersonated dials and ztls dials never fall back. A dial that falls back returns a `*ztls.Conn` instead of a `*tls.Conn`, which `WithTLSData` doesn't record. If the retry fails too, the error includes both failures.

To disable the fallback, set `DisableZtlsFallback` in the options or `DISABLE_ZTLS_FALLBACK=true` in the environment. Either one is enough.

# Example

Expand Down
129 changes: 129 additions & 0 deletions fastdialer/dial_result_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package fastdialer

import (
"context"
"net"
"sync"
"testing"
"time"

"github.com/stretchr/testify/require"
"golang.org/x/net/proxy"
)

type dialResultKey struct{}

type dialResult struct {
tag any
hostname, port string
err error
}

func TestOnDialResultReportsEachConnectionAttempt(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
go func() {
for {
conn, err := listener.Accept()
if err != nil {
return
}
_ = conn.Close()
}
}()
_, port, err := net.SplitHostPort(listener.Addr().String())
require.NoError(t, err)

var mu sync.Mutex
var results []dialResult
options := DefaultOptions
options.DialerTimeout = 2 * time.Second
options.OnDialResult = func(ctx context.Context, hostname, port string, err error) {
mu.Lock()
defer mu.Unlock()
results = append(results, dialResult{tag: ctx.Value(dialResultKey{}), hostname: hostname, port: port, err: err})
}
dialer, err := NewDialer(options)
require.NoError(t, err)
defer dialer.Close()

// the caller's context reaches the callback, so it can tell its own dials apart
ctx := context.WithValue(context.Background(), dialResultKey{}, "scan")
conn, err := dialer.Dial(ctx, "tcp", listener.Addr().String())
require.NoError(t, err)
_ = conn.Close()

require.NoError(t, listener.Close())
_, err = dialer.Dial(ctx, "tcp", listener.Addr().String())
require.Error(t, err)

mu.Lock()
defer mu.Unlock()
require.Len(t, results, 2)
require.Equal(t, dialResult{tag: "scan", hostname: "127.0.0.1", port: port}, results[0])
require.Equal(t, "scan", results[1].tag)
require.Equal(t, "127.0.0.1", results[1].hostname)
require.Equal(t, port, results[1].port)
require.Error(t, results[1].err)
}

// A target that never got as far as a connection attempt is not reported:
// nothing was asked of the host.
func TestOnDialResultSkipsTargetsNeverDialed(t *testing.T) {
called := false
options := DefaultOptions
options.OnDialResult = func(context.Context, string, string, error) { called = true }
dialer, err := NewDialer(options)
require.NoError(t, err)
defer dialer.Close()

_, err = dialer.Dial(context.Background(), "tcp", "127.0.0.1")
require.ErrorIs(t, err, NoPortSpecifiedError)
require.False(t, called)
}

// nilAddrConn reports no remote address.
type nilAddrConn struct{ net.Conn }

func (nilAddrConn) RemoteAddr() net.Addr { return nil }

type nilAddrProxy struct{ conn net.Conn }

func (p *nilAddrProxy) Dial(network, addr string) (net.Conn, error) {
client, server := net.Pipe()
_ = server.Close()
p.conn = nilAddrConn{Conn: client}
return p.conn, nil
}

// A connection with no remote address is a failed dial, so the callback
// reports that error rather than a success.
func TestOnDialResultReportsMissingRemoteAddr(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() { _ = listener.Close() })

pdialer := &nilAddrProxy{}
t.Cleanup(func() {
if pdialer.conn != nil {
_ = pdialer.conn.Close()
}
})
var pd proxy.Dialer = pdialer
var got error
options := DefaultOptions
options.ProxyDialer = &pd
options.OnDialResult = func(_ context.Context, _, _ string, err error) { got = err }

dialer, err := NewDialer(options)
require.NoError(t, err)
defer dialer.Close()

conn, err := dialer.Dial(context.Background(), "tcp", listener.Addr().String())
if conn != nil {
_ = conn.Close()
}
require.Error(t, err)
require.Contains(t, err.Error(), "remote address is nil")
require.Equal(t, err, got)
}
36 changes: 17 additions & 19 deletions fastdialer/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/projectdiscovery/retryabledns"
cryptoutil "github.com/projectdiscovery/utils/crypto"
"github.com/projectdiscovery/utils/env"
"github.com/projectdiscovery/utils/errkit"
"github.com/zmap/zcrypto/encoding/asn1"
ztls "github.com/zmap/zcrypto/tls"
"golang.org/x/net/proxy"
Expand Down Expand Up @@ -165,7 +164,6 @@ func NewDialer(options Options) (*Dialer, error) {
dialer = &net.Dialer{
Timeout: options.DialerTimeout,
KeepAlive: options.DialerKeepAlive,
DualStack: true,
}
}

Expand Down Expand Up @@ -296,6 +294,23 @@ func (d *Dialer) DialTLSWithConfig(ctx context.Context, network, address string,
}

// DialTLSWithConfigImpersonate dials tls with impersonation
//
// The config applies as in DialTLSWithConfig, except that the impersonated
// ClientHello decides what it offers, such as cipher suites and curves. Only
// TLS 1.3 sessions are stored in config.ClientSessionCache, and they resume
// only when the ClientHello has a pre_shared_key extension. utls can't resume
// a session when the server asks for another key share, so that dial fails
// and drops the session.
//
// If config.NextProtos is set, it replaces the protocols in the ALPN extension
// of the impersonated ClientHello, which can change its JA4 fingerprint but not
// its JA3 one. A ClientHello without ALPN still sends none. If NextProtos is
// empty, the ClientHello keeps its own protocols.
//
// config.MinVersion and config.MaxVersion limit the version that the server
// may select, but the ClientHello still offers its own versions, so the dial
// fails when the server selects one outside them. A zero MinVersion or
// MaxVersion leaves that limit to the ClientHello.
func (d *Dialer) DialTLSWithConfigImpersonate(ctx context.Context, network, address string, config *tls.Config, impersonate impersonate.Strategy, identity *impersonate.Identity) (conn net.Conn, err error) {
return d.dial(ctx, &dialOptions{
network: network,
Expand All @@ -311,23 +326,6 @@ func (d *Dialer) DialTLSWithConfigImpersonate(ctx context.Context, network, addr

// DialZTLSWithConfig dials ztls with config
func (d *Dialer) DialZTLSWithConfig(ctx context.Context, network, address string, config *ztls.Config) (conn net.Conn, err error) {
// ztls doesn't support tls13
if IsTLS13(config) {
stdTLSConfig, err := AsTLSConfig(config)
if err != nil {
return nil, errkit.Wrap(err, "could not convert ztls config to tls config")
}
return d.dial(ctx, &dialOptions{
network: network,
address: address,
shouldUseTLS: true,
shouldUseZTLS: false,
tlsconfig: stdTLSConfig,
ztlsconfig: nil,
impersonateStrategy: impersonate.None,
impersonateIdentity: nil,
})
}
return d.dial(ctx, &dialOptions{
network: network,
address: address,
Expand Down
Loading
Loading