From b837a77105e16b231f7f6e35cc9234e31958d261 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Thu, 28 May 2026 12:30:46 -0300 Subject: [PATCH 01/14] feat: enabling dnstt with a few fixes - Goroutine storm fixed: DNSTT instances now created lazily inside pool workers (max 10 at a time = 320 goroutines, down from 5,216) - RT caching: probe's http.Transport reused for requests, avoiding a second CONNECT tunnel that causes 502 on the same smux session - probeCancelFn: Close() cancels in-progress probe workers promptly so their DoH goroutines don't starve subsequent probes - waitFor raised to 2 minutes to allow full session establishment (~20-60s) - DNSTT type alias exported so callers don't need a direct import of github.com/getlantern/dnstt --- kindling/dnstt/parser.go | 129 ++++++++++++++++++++++------------ kindling/dnstt/parser_test.go | 8 +-- 2 files changed, 88 insertions(+), 49 deletions(-) diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index e9690a0c..125a9c8c 100644 --- a/kindling/dnstt/parser.go +++ b/kindling/dnstt/parser.go @@ -30,6 +30,10 @@ import ( "github.com/getlantern/radiance/traces" ) +// DNSTT is an alias for the upstream transport interface, re-exported so +// callers can type variables without importing github.com/getlantern/dnstt directly. +type DNSTT = dnstt.DNSTT + type dnsttConfig struct { Domain string `yaml:"domain"` // DNS tunnel domain, e.g., "t.iantem.io" PublicKey string `yaml:"publicKey"` // DNSTT server public key @@ -81,21 +85,10 @@ func DNSTTOptions(ctx context.Context, localConfigFilepath string, logger io.Wri slog.Warn("failed to read local dnstt config file", slog.Any("error", err), slog.String("filepath", localConfigFilepath)) } } - tunnels := make([]*dnsTunnel, 0) - for _, opt := range options { - dnst, err := newDNSTT(opt) - if err != nil { - slog.Warn("failed to build dnstt", slog.Any("error", err)) - continue - } - - tunnels = append(tunnels, &dnsTunnel{DNSTT: dnst}) - } - m := &multipleDNSTTTransport{ tunChan: make(chan *dnsTunnel, 400), stopChan: make(chan struct{}), - options: tunnels, + configs: options, } m.crawlOnce.Do(func() { go func() { @@ -247,7 +240,7 @@ func parseDNSTTConfigs(gzipyml []byte) ([]dnsttConfig, error) { return cfgs, nil } -var waitFor = 30 * time.Second +var waitFor = 2 * time.Minute func (m *multipleDNSTTTransport) findWorkingDNSTunnels() { // trying all dns tunnels available @@ -264,36 +257,46 @@ func (m *multipleDNSTTTransport) findWorkingDNSTunnels() { } func (m *multipleDNSTTTransport) tryAllDNSTunnels() { - slog.Debug("selecting dnstt options with active probing", slog.Int("options", len(m.options))) + slog.Debug("selecting dnstt options with active probing", slog.Int("options", len(m.configs))) - if len(m.options) == 0 { + if len(m.configs) == 0 { slog.Debug("no dns tunnel options available") return } pondCtx, cancel := context.WithTimeout(context.Background(), waitFor) + m.probeCancelMx.Lock() + m.probeCancelFn = cancel + m.probeCancelMx.Unlock() defer cancel() - // Limit concurrency to something reasonable poolSize := 10 - if len(m.options) < poolSize { - poolSize = len(m.options) + if len(m.configs) < poolSize { + poolSize = len(m.configs) } pool := pond.New(poolSize, 10, pond.Context(pondCtx)) - for _, dnst := range m.options { + for _, cfg := range m.configs { + cfg := cfg pool.Submit(func() { if m.closed.Load() { slog.Debug("closed, stop testing") - dnst.markFailed() - go dnst.Close() return } - rt, err := dnst.NewRoundTripper(pondCtx, "") + + // Instances are created here, not at startup, so only poolSize DNSTT + // instances (and their goroutines) are active at any one time. + dnstImpl, err := newDNSTT(cfg) + if err != nil { + slog.Debug("failed to create dnstt instance", slog.Any("error", err)) + return + } + tun := &dnsTunnel{DNSTT: dnstImpl} + + rt, err := tun.NewRoundTripper(pondCtx, "") if err != nil { slog.Debug("failed to create round tripper", slog.Any("error", err)) - dnst.markFailed() - go dnst.Close() + go tun.Close() return } @@ -305,39 +308,37 @@ func (m *multipleDNSTTTransport) tryAllDNSTunnels() { req, err := http.NewRequestWithContext(pondCtx, http.MethodGet, "https://www.gstatic.com/generate_204", http.NoBody) if err != nil { slog.Debug("failed to create request", slog.Any("error", err)) - dnst.markFailed() - go dnst.Close() + go tun.Close() return } resp, err := client.Do(req) if err != nil { slog.Debug("dnstt test request failed", slog.Any("error", err)) - dnst.markFailed() - go dnst.Close() + go tun.Close() return } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { slog.Debug("dnstt test returned non-2xx", slog.Int("status", resp.StatusCode)) - dnst.markFailed() - go dnst.Close() + go tun.Close() return } if m.closed.Load() { slog.Debug("closed, stop testing") - dnst.markFailed() - go dnst.Close() + go tun.Close() return } - // Successful tunnel slog.Debug("adding successful tun to channel") - dnst.markSucceeded() + tun.setProbeRT(rt) + tun.markSucceeded() if !m.closed.Load() { - m.tunChan <- dnst + m.tunChan <- tun + } else { + go tun.Close() } }) } @@ -350,14 +351,42 @@ type multipleDNSTTTransport struct { stopChan chan struct{} stopChanOnce sync.Once closed atomic.Bool - options []*dnsTunnel + configs []dnsttConfig + + // probeCancelFn cancels the pondCtx for the in-progress tryAllDNSTunnels + // call. Stored so Close() can abort probe workers promptly, preventing + // their DoH goroutines from competing with a subsequent transport's probe. + probeCancelFn context.CancelFunc + probeCancelMx sync.Mutex } type dnsTunnel struct { dnstt.DNSTT - // lastSucceeded: the most recent time at which this DNS tunnel succeeded lastSucceeded time.Time mx sync.RWMutex + + // probeRT caches the round tripper established during the probe. + // Reusing it avoids opening a second CONNECT tunnel on the same smux + // session, which fails with 502 when the server limits concurrent + // outbound connections per session. + probeRT http.RoundTripper + probeRTMx sync.Mutex +} + +func (t *dnsTunnel) setProbeRT(rt http.RoundTripper) { + t.probeRTMx.Lock() + defer t.probeRTMx.Unlock() + t.probeRT = rt +} + +func (t *dnsTunnel) getRoundTripper(ctx context.Context, addr string) (http.RoundTripper, error) { + t.probeRTMx.Lock() + rt := t.probeRT + t.probeRTMx.Unlock() + if rt != nil { + return rt, nil + } + return t.NewRoundTripper(ctx, addr) } func (t *dnsTunnel) markSucceeded() { @@ -410,7 +439,7 @@ type connectedRoundtripper struct { } func (c *connectedRoundtripper) RoundTrip(req *http.Request) (*http.Response, error) { - rt, err := c.t.NewRoundTripper(c.ctx, c.addr) + rt, err := c.t.getRoundTripper(c.ctx, c.addr) if err != nil { slog.DebugContext(c.ctx, "failed to create dnstt round tripper", slog.Any("error", err)) c.t.markFailed() @@ -434,12 +463,22 @@ func (m *multipleDNSTTTransport) Close() error { m.stopChanOnce.Do(func() { close(m.stopChan) }) - for _, dnst := range m.options { - go func() { - if err := dnst.Close(); err != nil { - slog.Error("failed to close dns tunnel", slog.Any("error", err)) - } - }() + m.probeCancelMx.Lock() + if m.probeCancelFn != nil { + m.probeCancelFn() + } + m.probeCancelMx.Unlock() + // Probe workers close their own instances on failure, so only successful tunnels land here. + for { + select { + case tun := <-m.tunChan: + go func() { + if err := tun.Close(); err != nil { + slog.Error("failed to close dns tunnel", slog.Any("error", err)) + } + }() + default: + return nil + } } - return nil } diff --git a/kindling/dnstt/parser_test.go b/kindling/dnstt/parser_test.go index 99d1dc0a..b76164a5 100644 --- a/kindling/dnstt/parser_test.go +++ b/kindling/dnstt/parser_test.go @@ -132,7 +132,7 @@ func TestDNSTTOptions(t *testing.T) { tr, ok := dnst.(*multipleDNSTTTransport) require.True(t, ok) - assert.GreaterOrEqual(t, len(tr.options), 1) + assert.GreaterOrEqual(t, len(tr.configs), 1) assert.NoError(t, dnst.Close()) }) @@ -147,7 +147,7 @@ func TestDNSTTOptions(t *testing.T) { tr, ok := dnst.(*multipleDNSTTTransport) require.True(t, ok) - assert.Len(t, tr.options, 1) + assert.Len(t, tr.configs, 1) assert.NoError(t, dnst.Close()) }) @@ -164,7 +164,7 @@ func TestDNSTTOptions(t *testing.T) { tr, ok := dnst.(*multipleDNSTTTransport) require.True(t, ok) - assert.GreaterOrEqual(t, len(tr.options), 1) + assert.GreaterOrEqual(t, len(tr.configs), 1) assert.NoError(t, dnst.Close()) }) @@ -175,7 +175,7 @@ func TestDNSTTOptions(t *testing.T) { assert.NoError(t, err) tr, ok := dnst.(*multipleDNSTTTransport) require.True(t, ok) - assert.GreaterOrEqual(t, len(tr.options), 1) + assert.GreaterOrEqual(t, len(tr.configs), 1) assert.NoError(t, dnst.Close()) }) } From 5b93d0a80b78c0868bf1b2b33d2174f2cc90d150 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Thu, 28 May 2026 12:31:42 -0300 Subject: [PATCH 02/14] fix: using the latest dnstt version also running go mod tidy --- go.mod | 38 +++++++++++++-------------- go.sum | 82 ++++++++++++++++++++++++++++------------------------------ 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/go.mod b/go.mod index 7638fe24..00d74c0f 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/alitto/pond v1.9.2 github.com/getlantern/amp v0.0.0-20260305201851-782bc8045e58 github.com/getlantern/common v1.2.1-0.20260326210434-cb69537aaf46 - github.com/getlantern/dnstt v0.0.0-20260112160750-05100563bd0d + github.com/getlantern/dnstt v0.0.0-20260511153124-dec6f436f3e5 github.com/getlantern/domainfront v0.0.0-20260419161617-0bff0b2169f4 github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694 github.com/getlantern/kindling v0.0.0-20260516120759-a9712f95df03 @@ -48,11 +48,11 @@ require ( github.com/sagernet/sing-box v1.12.22 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 - go.opentelemetry.io/otel/sdk v1.41.0 - go.opentelemetry.io/otel/sdk/metric v1.41.0 - golang.org/x/term v0.40.0 + go.opentelemetry.io/otel/sdk v1.43.0 + go.opentelemetry.io/otel/sdk/metric v1.43.0 + golang.org/x/term v0.41.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/protobuf v1.36.11 gopkg.in/natefinch/lumberjack.v2 v2.2.1 @@ -214,16 +214,16 @@ require ( gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2 v2.11.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/mock v0.5.2 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect golang.getoutline.org/sdk v0.0.21 // indirect golang.getoutline.org/sdk/x v0.1.0 // indirect - golang.org/x/text v0.34.0 // indirect + golang.org/x/text v0.35.0 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 // indirect modernc.org/libc v1.22.3 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect @@ -286,24 +286,24 @@ require ( github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/zeebo/blake3 v0.2.4 // indirect - go.opentelemetry.io/otel v1.41.0 + go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0 - go.opentelemetry.io/otel/metric v1.41.0 - go.opentelemetry.io/otel/trace v1.41.0 + go.opentelemetry.io/otel/metric v1.43.0 + go.opentelemetry.io/otel/trace v1.43.0 go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.48.0 + golang.org/x/crypto v0.49.0 golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.50.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/tools v0.42.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.79.2 + google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect + google.golang.org/grpc v1.80.0 gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 88e52158..c555ddd4 100644 --- a/go.sum +++ b/go.sum @@ -232,8 +232,8 @@ github.com/getlantern/common v1.2.1-0.20260326210434-cb69537aaf46 h1:Ab2esudqgFz github.com/getlantern/common v1.2.1-0.20260326210434-cb69537aaf46/go.mod h1:eSSuV4bMPgQJnczBw+KWWqWNo1itzmVxC++qUBPRTt0= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= -github.com/getlantern/dnstt v0.0.0-20260112160750-05100563bd0d h1:TrauJ2jdJqOAHyQB5wIL0kWN/dipqKagERE1I/TRVSY= -github.com/getlantern/dnstt v0.0.0-20260112160750-05100563bd0d/go.mod h1:LA7cwZQtgXxBJdSJDj2ZgQNo/UY3Qa7nxNxzOuMMIyw= +github.com/getlantern/dnstt v0.0.0-20260511153124-dec6f436f3e5 h1:h0x2koyqXkSfOO8FbnWrGC89HE+Y3Tt2Mogavs7x6iI= +github.com/getlantern/dnstt v0.0.0-20260511153124-dec6f436f3e5/go.mod h1:eqFsaq+WT5Q6NQGP24A94RuBhIZ8+wAcbBo7Ukqf3Ms= github.com/getlantern/domainfront v0.0.0-20260419161617-0bff0b2169f4 h1:/Q9FJvKPyuXfH6tfA+C+t9/AbvGWs3Yp9iqI74FYvb4= github.com/getlantern/domainfront v0.0.0-20260419161617-0bff0b2169f4/go.mod h1:nsdIvgenGUqPKnRFjkssbfxnV/WYWyC0c/t15qGym/A= github.com/getlantern/errors v1.0.4 h1:i2iR1M9GKj4WuingpNqJ+XQEw6i6dnAgKAmLj6ZB3X0= @@ -248,8 +248,6 @@ github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694 h1:iLWm6S/4 github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694/go.mod h1:ag5g9aWUw2FJcX5RVRpJ9EBQBy5yJuy2WXDouIn/m4w= github.com/getlantern/kindling v0.0.0-20260516120759-a9712f95df03 h1:dUTN7mnTTBcSvsURNs1rTlyKrD1uXUEPqxEZDfl+hb4= github.com/getlantern/kindling v0.0.0-20260516120759-a9712f95df03/go.mod h1:TGTxpoNVwc8Be4qkBNtf5oj2psJaEIZEq47GOPS7zkA= -github.com/getlantern/lantern-box v0.0.83 h1:8DO/24pGilGfqEjWHEF4M/iqI/IVdR3XIu5gCyA6iN0= -github.com/getlantern/lantern-box v0.0.83/go.mod h1:6SO1p22tAq9y8JLjNnAbr4/GZ4VjmlcQGYn0qF4aD/k= github.com/getlantern/lantern-box v0.0.84 h1:y+nezmu0LZDlzcS2A4oKDu3f1UTFAgA24vT1htvEiX0= github.com/getlantern/lantern-box v0.0.84/go.mod h1:6SO1p22tAq9y8JLjNnAbr4/GZ4VjmlcQGYn0qF4aD/k= github.com/getlantern/lantern-water v0.0.0-20260520145825-958775d51395 h1:grfGavAUp2E9w9ZoJuM3FyWyQ0sCJ64V4ZMKtZKRqTc= @@ -802,24 +800,24 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= -go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= -go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0 h1:VO3BL6OZXRQ1yQc8W6EVfJzINeJ35BkiHx4MYfoQf44= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0/go.mod h1:qRDnJ2nv3CQXMK2HUd9K9VtvedsPAce3S+/4LZHjX/s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 h1:mq/Qcf28TWz719lE3/hMB4KkyDuLJIvgJnFGcd0kEUI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0/go.mod h1:yk5LXEYhsL2htyDNJbEq7fWzNEigeEdV5xBF/Y+kAv0= -go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= -go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= -go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= -go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= -go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= -go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y= -go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= -go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= @@ -855,8 +853,8 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -891,8 +889,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -923,8 +921,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -939,8 +937,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -982,8 +980,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -994,8 +992,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1010,8 +1008,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= @@ -1044,8 +1042,8 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1056,8 +1054,8 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdI golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -1085,10 +1083,10 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 h1:U8orV30l6KpDsi9dxU0CoJZGbjS8EEpw+6ba+XwGPQA= +google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348/go.mod h1:Yzdzr5OOZFgSsEV2D/Xi9NL3bszpXFAg0hFJiRohcD8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1099,8 +1097,8 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= -google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From d1cfdebf4459a24bbe87c9ae20d8180553807f21 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Thu, 28 May 2026 12:47:53 -0300 Subject: [PATCH 03/14] fix: enabling dnstt transport --- kindling/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindling/client.go b/kindling/client.go index 73fabf73..4c05c356 100644 --- a/kindling/client.go +++ b/kindling/client.go @@ -33,7 +33,7 @@ var ( // EnabledTransports gates which transports NewKindling wires up. Intended for tests; not a // production toggle. EnabledTransports = map[kindling.TransportName]bool{ - kindling.TransportDNSTunnel: false, + kindling.TransportDNSTunnel: true, kindling.TransportAMP: true, kindling.TransportSmart: true, kindling.TransportDomainfront: true, From 9238d6ead68ffd3d2b18252989da272ad202a152 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Fri, 29 May 2026 11:14:09 -0300 Subject: [PATCH 04/14] fix: improving log messages --- kindling/dnstt/parser.go | 46 ++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index 125a9c8c..866affdc 100644 --- a/kindling/dnstt/parser.go +++ b/kindling/dnstt/parser.go @@ -194,6 +194,16 @@ func onNewDNSTTConfig(configFilepath string, gzippedYML []byte) error { return atomicfile.WriteFile(configFilepath, gzippedYML, fileperm.File) } +func cfgResolver(cfg dnsttConfig) string { + if cfg.DoHResolver != nil { + return *cfg.DoHResolver + } + if cfg.DoTResolver != nil { + return *cfg.DoTResolver + } + return "" +} + func newDNSTT(cfg dnsttConfig) (dnstt.DNSTT, error) { opts := make([]dnstt.Option, 0) if cfg.Domain != "" { @@ -240,7 +250,7 @@ func parseDNSTTConfigs(gzipyml []byte) ([]dnsttConfig, error) { return cfgs, nil } -var waitFor = 2 * time.Minute +var waitFor = 5 * time.Minute func (m *multipleDNSTTTransport) findWorkingDNSTunnels() { // trying all dns tunnels available @@ -286,59 +296,59 @@ func (m *multipleDNSTTTransport) tryAllDNSTunnels() { // Instances are created here, not at startup, so only poolSize DNSTT // instances (and their goroutines) are active at any one time. + resolver := cfgResolver(cfg) dnstImpl, err := newDNSTT(cfg) if err != nil { - slog.Debug("failed to create dnstt instance", slog.Any("error", err)) + slog.Debug("failed to create dnstt instance", slog.String("domain", cfg.Domain), slog.String("resolver", resolver), slog.Any("error", err)) return } tun := &dnsTunnel{DNSTT: dnstImpl} rt, err := tun.NewRoundTripper(pondCtx, "") if err != nil { - slog.Debug("failed to create round tripper", slog.Any("error", err)) - go tun.Close() + slog.Debug("failed to create round tripper", slog.String("domain", cfg.Domain), slog.String("resolver", resolver), slog.Any("error", err)) + tun.Close() return } - client := &http.Client{ - Transport: rt, - Timeout: 15 * time.Second, - } + // 60 s covers DNSTT session establishment (~20-60 s over DoH) while + // still failing fast enough for unreachable resolvers (TCP timeout ≈30 s). + client := &http.Client{Transport: rt, Timeout: 60 * time.Second} req, err := http.NewRequestWithContext(pondCtx, http.MethodGet, "https://www.gstatic.com/generate_204", http.NoBody) if err != nil { - slog.Debug("failed to create request", slog.Any("error", err)) - go tun.Close() + slog.Debug("failed to create request", slog.String("domain", cfg.Domain), slog.String("resolver", resolver), slog.Any("error", err)) + tun.Close() return } resp, err := client.Do(req) if err != nil { - slog.Debug("dnstt test request failed", slog.Any("error", err)) - go tun.Close() + slog.Debug("dnstt probe failed", slog.String("domain", cfg.Domain), slog.String("resolver", resolver), slog.Any("error", err)) + tun.Close() return } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { - slog.Debug("dnstt test returned non-2xx", slog.Int("status", resp.StatusCode)) - go tun.Close() + slog.Debug("dnstt probe returned non-2xx", slog.String("domain", cfg.Domain), slog.String("resolver", resolver), slog.Int("status", resp.StatusCode)) + tun.Close() return } if m.closed.Load() { slog.Debug("closed, stop testing") - go tun.Close() + tun.Close() return } - slog.Debug("adding successful tun to channel") + slog.Debug("dnstt tunnel ready", slog.String("domain", cfg.Domain), slog.String("resolver", resolver)) tun.setProbeRT(rt) tun.markSucceeded() if !m.closed.Load() { m.tunChan <- tun } else { - go tun.Close() + tun.Close() } }) } @@ -468,7 +478,7 @@ func (m *multipleDNSTTTransport) Close() error { m.probeCancelFn() } m.probeCancelMx.Unlock() - // Probe workers close their own instances on failure, so only successful tunnels land here. + // Probe workers close their own failed instances synchronously, so only successful tunnels land here. for { select { case tun := <-m.tunChan: From f4d203185f07f856900c67607057bfc4e780ad2d Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Fri, 29 May 2026 16:16:57 -0300 Subject: [PATCH 05/14] feat: adding dnstt WithDialer option --- go.mod | 2 ++ go.sum | 2 -- kindling/dnstt/parser.go | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 00d74c0f..c01062c5 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,8 @@ replace github.com/tetratelabs/wazero => github.com/getlantern/wazero v1.11.0-wa replace github.com/refraction-networking/water => github.com/getlantern/water v0.7.1-alpha.0.20260309190745-bd547c14b4aa +replace github.com/getlantern/dnstt => ../dnstt + // replace github.com/getlantern/common => ../common // replace github.com/sagernet/sing => ../sing diff --git a/go.sum b/go.sum index c555ddd4..3b5b98e3 100644 --- a/go.sum +++ b/go.sum @@ -232,8 +232,6 @@ github.com/getlantern/common v1.2.1-0.20260326210434-cb69537aaf46 h1:Ab2esudqgFz github.com/getlantern/common v1.2.1-0.20260326210434-cb69537aaf46/go.mod h1:eSSuV4bMPgQJnczBw+KWWqWNo1itzmVxC++qUBPRTt0= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= -github.com/getlantern/dnstt v0.0.0-20260511153124-dec6f436f3e5 h1:h0x2koyqXkSfOO8FbnWrGC89HE+Y3Tt2Mogavs7x6iI= -github.com/getlantern/dnstt v0.0.0-20260511153124-dec6f436f3e5/go.mod h1:eqFsaq+WT5Q6NQGP24A94RuBhIZ8+wAcbBo7Ukqf3Ms= github.com/getlantern/domainfront v0.0.0-20260419161617-0bff0b2169f4 h1:/Q9FJvKPyuXfH6tfA+C+t9/AbvGWs3Yp9iqI74FYvb4= github.com/getlantern/domainfront v0.0.0-20260419161617-0bff0b2169f4/go.mod h1:nsdIvgenGUqPKnRFjkssbfxnV/WYWyC0c/t15qGym/A= github.com/getlantern/errors v1.0.4 h1:i2iR1M9GKj4WuingpNqJ+XQEw6i6dnAgKAmLj6ZB3X0= diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index 866affdc..7169d6af 100644 --- a/kindling/dnstt/parser.go +++ b/kindling/dnstt/parser.go @@ -23,6 +23,7 @@ import ( "github.com/goccy/go-yaml" "go.opentelemetry.io/otel" + "github.com/getlantern/radiance/bypass" "github.com/getlantern/radiance/common/atomicfile" "github.com/getlantern/radiance/common/fileperm" "github.com/getlantern/radiance/events" @@ -222,6 +223,11 @@ func newDNSTT(cfg dnsttConfig) (dnstt.DNSTT, error) { opts = append(opts, dnstt.WithUTLSDistribution(*cfg.UTLSDistribution)) } + // DNSTT is the last-resort transport, expected to work when the VPN is up + // but every other transport is blocked. Dialing through bypass keeps its + // DoH/DoT connections off the TUN so they don't loop back through the tunnel. + opts = append(opts, dnstt.WithDialer(bypass.DialContext)) + d, err := dnstt.NewDNSTT(opts...) if err != nil { return nil, fmt.Errorf("failed to build new dnstt: %w", err) From db6b76b6fc7d95511d3d42f80002f4e7e2120ae7 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:54:39 -0300 Subject: [PATCH 06/14] dnstt: fix tunnel exhaustion and add on-demand reprobing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace instant-death markFailed with a softer consecutiveFailures counter (5 strikes). When a tunnel fails, clear the cached probe round tripper so the next attempt gets a fresh smux stream instead of reusing a degraded transport. Close permanently dead tunnels instead of leaking them via 'continue'. isSucceeding now requires both that the tunnel has ever succeeded AND is under the failure threshold — the old || logic would recycle tunnels that had never worked. Add an on-demand probe trigger: when NewRoundTripper exhausts all usable tunnels it signals probeCh, which findWorkingDNSTunnels picks up to start a fresh cycle immediately instead of waiting for the timer. Reduce the probe interval from 30 min to 5 min so re-probes happen even without a trigger signal. Also wire domain/resolver into the dnsTunnel struct so RoundTrip logs identify which tunnel handled each request. --- kindling/dnstt/parser.go | 94 ++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 24 deletions(-) diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index 7169d6af..4ba33edf 100644 --- a/kindling/dnstt/parser.go +++ b/kindling/dnstt/parser.go @@ -87,8 +87,9 @@ func DNSTTOptions(ctx context.Context, localConfigFilepath string, logger io.Wri } } m := &multipleDNSTTTransport{ - tunChan: make(chan *dnsTunnel, 400), + tunChan: make(chan *dnsTunnel, 400), stopChan: make(chan struct{}), + probeCh: make(chan struct{}, 1), configs: options, } m.crawlOnce.Do(func() { @@ -259,14 +260,17 @@ func parseDNSTTConfigs(gzipyml []byte) ([]dnsttConfig, error) { var waitFor = 5 * time.Minute func (m *multipleDNSTTTransport) findWorkingDNSTunnels() { - // trying all dns tunnels available go m.tryAllDNSTunnels() + ticker := time.NewTicker(probeInterval) + defer ticker.Stop() for { select { case <-m.stopChan: slog.Debug("stopping parallel dialing dns tunnels") return - case <-time.After(30 * time.Minute): + case <-ticker.C: + m.tryAllDNSTunnels() + case <-m.probeCh: m.tryAllDNSTunnels() } } @@ -308,7 +312,7 @@ func (m *multipleDNSTTTransport) tryAllDNSTunnels() { slog.Debug("failed to create dnstt instance", slog.String("domain", cfg.Domain), slog.String("resolver", resolver), slog.Any("error", err)) return } - tun := &dnsTunnel{DNSTT: dnstImpl} + tun := &dnsTunnel{DNSTT: dnstImpl, domain: cfg.Domain, resolver: resolver} rt, err := tun.NewRoundTripper(pondCtx, "") if err != nil { @@ -317,11 +321,11 @@ func (m *multipleDNSTTTransport) tryAllDNSTunnels() { return } - // 60 s covers DNSTT session establishment (~20-60 s over DoH) while - // still failing fast enough for unreachable resolvers (TCP timeout ≈30 s). - client := &http.Client{Transport: rt, Timeout: 60 * time.Second} + // 180 s covers DNSTT session establishment (~20-60 s over DoH) and + // TLS handshake through 135-byte MTU tunnel (multiple round trips). + client := &http.Client{Transport: rt, Timeout: 180 * time.Second} - req, err := http.NewRequestWithContext(pondCtx, http.MethodGet, "https://www.gstatic.com/generate_204", http.NoBody) + req, err := http.NewRequestWithContext(pondCtx, http.MethodGet, "http://www.gstatic.com/generate_204", http.NoBody) if err != nil { slog.Debug("failed to create request", slog.String("domain", cfg.Domain), slog.String("resolver", resolver), slog.Any("error", err)) tun.Close() @@ -361,6 +365,8 @@ func (m *multipleDNSTTTransport) tryAllDNSTunnels() { pool.StopAndWaitFor(waitFor) } +const probeInterval = 5 * time.Minute + type multipleDNSTTTransport struct { crawlOnce sync.Once tunChan chan *dnsTunnel @@ -369,6 +375,11 @@ type multipleDNSTTTransport struct { closed atomic.Bool configs []dnsttConfig + // probeCh triggers an on-demand probe cycle when NewRoundTripper + // exhausts all available tunnels. Buffered (1) so a trigger is + // never lost but spurious duplicate triggers are dropped. + probeCh chan struct{} + // probeCancelFn cancels the pondCtx for the in-progress tryAllDNSTunnels // call. Stored so Close() can abort probe workers promptly, preventing // their DoH goroutines from competing with a subsequent transport's probe. @@ -376,15 +387,24 @@ type multipleDNSTTTransport struct { probeCancelMx sync.Mutex } +// maxTunnelFailures is the number of consecutive failures after which a +// tunnel is permanently discarded. A single failure is often transient +// (e.g. TLS handshake timeout through the slow DNS tunnel), so we give +// the tunnel several chances before giving up. +const maxTunnelFailures = 5 + type dnsTunnel struct { dnstt.DNSTT + domain string + resolver string lastSucceeded time.Time - mx sync.RWMutex + consecutiveFailures int + mx sync.RWMutex // probeRT caches the round tripper established during the probe. - // Reusing it avoids opening a second CONNECT tunnel on the same smux - // session, which fails with 502 when the server limits concurrent - // outbound connections per session. + // It is cleared when the tunnel enters a retry so a fresh round + // tripper (with a fresh smux stream) is created for the real + // transport. probeRT http.RoundTripper probeRTMx sync.Mutex } @@ -409,18 +429,30 @@ func (t *dnsTunnel) markSucceeded() { t.mx.Lock() defer t.mx.Unlock() t.lastSucceeded = time.Now() + t.consecutiveFailures = 0 } -func (t *dnsTunnel) markFailed() { +func (t *dnsTunnel) recordFailure() { t.mx.Lock() defer t.mx.Unlock() - t.lastSucceeded = time.Time{} + t.consecutiveFailures++ + if t.consecutiveFailures >= maxTunnelFailures { + t.lastSucceeded = time.Time{} + } +} + +// clearProbeRT invalidates the cached probe round tripper so the next +// call to getRoundTripper creates a fresh one with a new smux stream. +func (t *dnsTunnel) clearProbeRT() { + t.probeRTMx.Lock() + t.probeRT = nil + t.probeRTMx.Unlock() } func (t *dnsTunnel) isSucceeding() bool { t.mx.RLock() defer t.mx.RUnlock() - return t.lastSucceeded.After(time.Time{}) + return t.lastSucceeded.After(time.Time{}) && t.consecutiveFailures < maxTunnelFailures } // NewRoundTripper creates a new HTTP round tripper for the given address. @@ -430,21 +462,24 @@ func (m *multipleDNSTTTransport) NewRoundTripper(ctx context.Context, addr strin select { case <-ctx.Done(): return nil, ctx.Err() - // Add a case for the stop channel being called case <-m.stopChan: return nil, errors.New("dnstt stopped") case tun := <-m.tunChan: - // The tun may have stopped succeeding since we last checked, - // so only return it if it's still succeeding. if !tun.isSucceeding() { + // Permanently dead — close and remove. + tun.Close() continue } - - // Add the tun back to the channel. m.tunChan <- tun return &connectedRoundtripper{t: tun, ctx: ctx, addr: addr}, nil } } + // Exhausted the probe pool — request a fresh probe cycle so + // callers don't block for minutes until the next scheduled run. + select { + case m.probeCh <- struct{}{}: + default: + } return nil, fmt.Errorf("could not connect to any dns tunnel") } @@ -457,18 +492,29 @@ type connectedRoundtripper struct { func (c *connectedRoundtripper) RoundTrip(req *http.Request) (*http.Response, error) { rt, err := c.t.getRoundTripper(c.ctx, c.addr) if err != nil { - slog.DebugContext(c.ctx, "failed to create dnstt round tripper", slog.Any("error", err)) - c.t.markFailed() + slog.WarnContext(c.ctx, "dnstt roundtripper creation failed", + "domain", c.t.domain, "resolver", c.t.resolver, "error", err) + c.t.clearProbeRT() + c.t.recordFailure() return nil, err } resp, err := rt.RoundTrip(req) if err != nil { - slog.WarnContext(c.ctx, "dnstt roundtripper failed", slog.Any("error", err)) - c.t.markFailed() + // A failure is often transient (e.g. TLS handshake timeout). Don't + // kill the tunnel — clear the cached probe round tripper so the next + // attempt gets a fresh smux stream, and record the failure. The + // tunnel is discarded only after maxTunnelFailures consecutive + // failures. + c.t.clearProbeRT() + c.t.recordFailure() + slog.WarnContext(c.ctx, "dnstt roundtripper failed", + "domain", c.t.domain, "resolver", c.t.resolver, "error", err) return nil, err } + slog.DebugContext(c.ctx, "dnstt roundtripper succeeded", + "domain", c.t.domain, "resolver", c.t.resolver) c.t.markSucceeded() return resp, nil } From 8b7a70ef067dba40321321a97ea78b8fc403fa2b Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:38:05 -0300 Subject: [PATCH 07/14] dnstt: request 5-min race transport budget via RequestTimeout DNSTT sessions run over DNS at 135-byte MTU; a single TLS handshake can take 30-60 seconds through the tunnel. The race transport's default 80s GET budget and 3min POST budget are often too tight. Add RequestTimeout() returning 5 minutes so kindling's race transport gives this transport enough time to complete. The upstream kindling change (../kindling) wires this into the race budget. Also replace the kindling dependency with a local path so these changes take effect together. --- go.mod | 2 ++ kindling/dnstt/parser.go | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/go.mod b/go.mod index b4340c7a..19110665 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.26.2 replace github.com/sagernet/sing => github.com/getlantern/sing v0.7.18-lantern +replace github.com/getlantern/kindling => ../kindling + replace github.com/sagernet/sing-box => github.com/getlantern/sing-box-minimal v1.12.22-lantern replace github.com/sagernet/wireguard-go => github.com/getlantern/wireguard-go v0.0.1-beta.7.0.20251208214020-d78e69f1eff4 diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index 4ba33edf..88a8fc4f 100644 --- a/kindling/dnstt/parser.go +++ b/kindling/dnstt/parser.go @@ -367,6 +367,15 @@ func (m *multipleDNSTTTransport) tryAllDNSTunnels() { const probeInterval = 5 * time.Minute +// RequestTimeout returns the maximum time the race transport should wait for +// a single request through this DNSTT transport. DNSTT sessions are slow +// (DNS-tunneled, 135-byte MTU) — a single TLS handshake over the tunnel +// can take tens of seconds, so this budget is significantly longer than +// the race transport default. +func (m *multipleDNSTTTransport) RequestTimeout() time.Duration { + return 5 * time.Minute +} + type multipleDNSTTTransport struct { crawlOnce sync.Once tunChan chan *dnsTunnel From ed50bfe18302369dc1ebdc2dc6370edde29ea867 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:17:27 -0300 Subject: [PATCH 08/14] common: bump DefaultHTTPTimeout from 180s to 360s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The config fetcher and kindling HTTP client both use this timeout as an outer bound around the race transport. With DNSTT requesting a 5-minute race budget, the outer timeout of 180s was killing requests before the tunnel could complete — response headers were arriving but body reads were cut off mid-stream. 360s gives DNSTT 5min with 1min of headroom. --- common/constants.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.go b/common/constants.go index 6c2b1ca6..5e742a52 100644 --- a/common/constants.go +++ b/common/constants.go @@ -14,7 +14,7 @@ var Version = "dev" const ( Name = "lantern" - DefaultHTTPTimeout = (60 * time.Second) + DefaultHTTPTimeout = (360 * time.Second) // API URLs ProServerURL = "https://api.getiantem.org" From 368ce99d9e578250949b7355b85c956a463d2eef Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:23:42 -0300 Subject: [PATCH 09/14] kindling/dnstt: remove probeRT session caching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed the probeRT field from dnsTunnel and all related methods (setProbeRT, getRoundTripper, clearProbeRT). The probe still validates the tunnel but no longer caches its smux session — each real request now creates a fresh round tripper via NewRoundTripper. This fixes the 'io: read/write on closed pipe' cascade: when multiple concurrent requests shared the probe's cached smux session, a single KCP disconnect killed all streams simultaneously. With per-request sessions, each request has isolation — one dying session doesn't take down others. --- go.mod | 4 +-- go.sum | 2 -- kindling/dnstt/parser.go | 57 +++++++--------------------------------- 3 files changed, 11 insertions(+), 52 deletions(-) diff --git a/go.mod b/go.mod index 19110665..8500a884 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,6 @@ go 1.26.2 replace github.com/sagernet/sing => github.com/getlantern/sing v0.7.18-lantern -replace github.com/getlantern/kindling => ../kindling - replace github.com/sagernet/sing-box => github.com/getlantern/sing-box-minimal v1.12.22-lantern replace github.com/sagernet/wireguard-go => github.com/getlantern/wireguard-go v0.0.1-beta.7.0.20251208214020-d78e69f1eff4 @@ -16,6 +14,8 @@ replace github.com/refraction-networking/water => github.com/getlantern/water v0 replace github.com/getlantern/dnstt => ../dnstt +replace github.com/getlantern/kindling => ../kindling + // replace github.com/getlantern/common => ../common // replace github.com/sagernet/sing => ../sing diff --git a/go.sum b/go.sum index 97306b22..8ea8ce30 100644 --- a/go.sum +++ b/go.sum @@ -244,8 +244,6 @@ github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 h1:cSrD9ryDfTV2y github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770/go.mod h1:GOQsoDnEHl6ZmNIL+5uVo+JWRFWozMEp18Izcb++H+A= github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694 h1:iLWm6S/47Hfk7FjW6yaD+1h6kO7C/iauV0DkVia/bXU= github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694/go.mod h1:ag5g9aWUw2FJcX5RVRpJ9EBQBy5yJuy2WXDouIn/m4w= -github.com/getlantern/kindling v0.0.0-20260529141244-21f8b144afab h1:PitYhTvo3oHRKYl4pVAoOIN8bhM+Bw+JBWncMglvHSg= -github.com/getlantern/kindling v0.0.0-20260529141244-21f8b144afab/go.mod h1:TGTxpoNVwc8Be4qkBNtf5oj2psJaEIZEq47GOPS7zkA= github.com/getlantern/lantern-box v0.0.86 h1:myJa+Crg/oMgqSFhX7DOox4XcVIx8VFiPnkel8x8YT4= github.com/getlantern/lantern-box v0.0.86/go.mod h1:BVXPyEicSu7m4nQY1OHPkOZNj87M7sYrzmY9AgyiPkc= github.com/getlantern/lantern-water v0.0.0-20260520145825-958775d51395 h1:grfGavAUp2E9w9ZoJuM3FyWyQ0sCJ64V4ZMKtZKRqTc= diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index 88a8fc4f..b230fbca 100644 --- a/kindling/dnstt/parser.go +++ b/kindling/dnstt/parser.go @@ -352,14 +352,13 @@ func (m *multipleDNSTTTransport) tryAllDNSTunnels() { return } - slog.Debug("dnstt tunnel ready", slog.String("domain", cfg.Domain), slog.String("resolver", resolver)) - tun.setProbeRT(rt) - tun.markSucceeded() - if !m.closed.Load() { - m.tunChan <- tun - } else { - tun.Close() - } + slog.Debug("dnstt tunnel ready", slog.String("domain", cfg.Domain), slog.String("resolver", resolver)) + tun.markSucceeded() + if !m.closed.Load() { + m.tunChan <- tun + } else { + tun.Close() + } }) } pool.StopAndWaitFor(waitFor) @@ -406,32 +405,9 @@ type dnsTunnel struct { dnstt.DNSTT domain string resolver string - lastSucceeded time.Time + lastSucceeded time.Time consecutiveFailures int mx sync.RWMutex - - // probeRT caches the round tripper established during the probe. - // It is cleared when the tunnel enters a retry so a fresh round - // tripper (with a fresh smux stream) is created for the real - // transport. - probeRT http.RoundTripper - probeRTMx sync.Mutex -} - -func (t *dnsTunnel) setProbeRT(rt http.RoundTripper) { - t.probeRTMx.Lock() - defer t.probeRTMx.Unlock() - t.probeRT = rt -} - -func (t *dnsTunnel) getRoundTripper(ctx context.Context, addr string) (http.RoundTripper, error) { - t.probeRTMx.Lock() - rt := t.probeRT - t.probeRTMx.Unlock() - if rt != nil { - return rt, nil - } - return t.NewRoundTripper(ctx, addr) } func (t *dnsTunnel) markSucceeded() { @@ -450,14 +426,6 @@ func (t *dnsTunnel) recordFailure() { } } -// clearProbeRT invalidates the cached probe round tripper so the next -// call to getRoundTripper creates a fresh one with a new smux stream. -func (t *dnsTunnel) clearProbeRT() { - t.probeRTMx.Lock() - t.probeRT = nil - t.probeRTMx.Unlock() -} - func (t *dnsTunnel) isSucceeding() bool { t.mx.RLock() defer t.mx.RUnlock() @@ -499,23 +467,16 @@ type connectedRoundtripper struct { } func (c *connectedRoundtripper) RoundTrip(req *http.Request) (*http.Response, error) { - rt, err := c.t.getRoundTripper(c.ctx, c.addr) + rt, err := c.t.NewRoundTripper(c.ctx, c.addr) if err != nil { slog.WarnContext(c.ctx, "dnstt roundtripper creation failed", "domain", c.t.domain, "resolver", c.t.resolver, "error", err) - c.t.clearProbeRT() c.t.recordFailure() return nil, err } resp, err := rt.RoundTrip(req) if err != nil { - // A failure is often transient (e.g. TLS handshake timeout). Don't - // kill the tunnel — clear the cached probe round tripper so the next - // attempt gets a fresh smux stream, and record the failure. The - // tunnel is discarded only after maxTunnelFailures consecutive - // failures. - c.t.clearProbeRT() c.t.recordFailure() slog.WarnContext(c.ctx, "dnstt roundtripper failed", "domain", c.t.domain, "resolver", c.t.resolver, "error", err) From 367df841141373c83313435fbf4b24c7f13e477b Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:50:13 -0300 Subject: [PATCH 10/14] kindling/dnstt: add MaxLength() returning 10KB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sets a 10KB body size cap on the DNSTT transport so the race transport skips it for oversized payloads (e.g. 1.4MB issue report protobufs). DNS tunnels have a 135-byte MTU — 10KB already needs ~75 DNS queries and nearly exhausts the 5-minute RequestTimeout budget. Larger payloads should route through another transport. --- kindling/dnstt/parser.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index b230fbca..78cde7b8 100644 --- a/kindling/dnstt/parser.go +++ b/kindling/dnstt/parser.go @@ -375,6 +375,14 @@ func (m *multipleDNSTTTransport) RequestTimeout() time.Duration { return 5 * time.Minute } +// MaxLength returns the maximum request body size this transport supports. +// DNS tunnels have a 135-byte MTU — a 10 KB body requires ~75 DNS queries +// which already pushes the RequestTimeout budget. Larger bodies are routed +// to a different transport. +func (m *multipleDNSTTTransport) MaxLength() int { + return 10_000 +} + type multipleDNSTTTransport struct { crawlOnce sync.Once tunChan chan *dnsTunnel From b4a0eff2ed2d4c9671b6c31d05e0e9c1c284ceb2 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:13:19 -0300 Subject: [PATCH 11/14] =?UTF-8?q?kindling:=20reorder=20transports=20?= =?UTF-8?q?=E2=80=94=20DNSTT=20last=20(lowest=20priority)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves DNSTT registration after Smart/AMP/Domainfront so it only races if the other transports fail to connect. Also re-enables all transports now that DNSTT is no longer the sole transport. --- kindling/client.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kindling/client.go b/kindling/client.go index 4c05c356..294b3aa2 100644 --- a/kindling/client.go +++ b/kindling/client.go @@ -150,6 +150,12 @@ func NewKindling(dataDir string) (kindling.Kindling, error) { } } + if enabled := EnabledTransports[kindling.TransportSmart]; enabled { + // "pro-server" calls still target api.getiantem.org; everything + // else uses df.iantem.io. + kindlingOptions = append(kindlingOptions, kindling.WithProxyless("df.iantem.io", "api.getiantem.org")) + } + if enabled := EnabledTransports[kindling.TransportDNSTunnel]; enabled { dnsttOptions, err := dnstt.DNSTTOptions(updaterCtx, filepath.Join(dataDir, "dnstt.yml.gz"), logger) if err != nil { @@ -162,12 +168,6 @@ func NewKindling(dataDir string) (kindling.Kindling, error) { } } - if enabled := EnabledTransports[kindling.TransportSmart]; enabled { - // "pro-server" calls still target api.getiantem.org; everything - // else uses df.iantem.io. - kindlingOptions = append(kindlingOptions, kindling.WithProxyless("df.iantem.io", "api.getiantem.org")) - } - stopUpdater = cancel return kindling.NewKindling("radiance", kindlingOptions...) } From efe40ab6bc54845b94b2c801cf324f2579049be6 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:10:09 -0300 Subject: [PATCH 12/14] kindling/dnstt: create smux session eagerly in NewRoundTripper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, NewRoundTripper returned a connectedRoundtripper without establishing any connection — the actual smux session was created lazily in RoundTrip. This meant DNSTT's NewRoundTripper returned instantly and always won the connection race against Smart/AMP/Domainfront that actually pre-connect. Now NewRoundTripper calls tun.NewRoundTripper during the connect phase so DNSTT pays the real connection cost (KCP + smux handshake) during the race. connectedRoundtripper stores the pre-built RoundTripper and uses it directly. --- kindling/dnstt/parser.go | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index 78cde7b8..22a5ff55 100644 --- a/kindling/dnstt/parser.go +++ b/kindling/dnstt/parser.go @@ -440,8 +440,9 @@ func (t *dnsTunnel) isSucceeding() bool { return t.lastSucceeded.After(time.Time{}) && t.consecutiveFailures < maxTunnelFailures } -// NewRoundTripper creates a new HTTP round tripper for the given address. -// It manages session creation and reuse. +// NewRoundTripper creates a pre-connected HTTP round tripper for the given +// address. It blocks until a KCP session and smux stream are established so +// that the race transport can fairly compare connection latencies. func (m *multipleDNSTTTransport) NewRoundTripper(ctx context.Context, addr string) (http.RoundTripper, error) { for range 6 { select { @@ -451,47 +452,42 @@ func (m *multipleDNSTTTransport) NewRoundTripper(ctx context.Context, addr strin return nil, errors.New("dnstt stopped") case tun := <-m.tunChan: if !tun.isSucceeding() { - // Permanently dead — close and remove. tun.Close() continue } + rt, err := tun.NewRoundTripper(ctx, addr) + if err != nil { + tun.recordFailure() + slog.WarnContext(ctx, "dnstt roundtripper creation failed during connect", + "domain", tun.domain, "resolver", tun.resolver, "error", err) + continue + } m.tunChan <- tun - return &connectedRoundtripper{t: tun, ctx: ctx, addr: addr}, nil + return &connectedRoundtripper{t: tun, rt: rt}, nil } } - // Exhausted the probe pool — request a fresh probe cycle so - // callers don't block for minutes until the next scheduled run. select { case m.probeCh <- struct{}{}: default: } - return nil, fmt.Errorf("could not connect to any dns tunnel") + return nil, errors.New("no working dnstt tunnels available") } type connectedRoundtripper struct { - t *dnsTunnel - ctx context.Context - addr string + t *dnsTunnel + rt http.RoundTripper } func (c *connectedRoundtripper) RoundTrip(req *http.Request) (*http.Response, error) { - rt, err := c.t.NewRoundTripper(c.ctx, c.addr) - if err != nil { - slog.WarnContext(c.ctx, "dnstt roundtripper creation failed", - "domain", c.t.domain, "resolver", c.t.resolver, "error", err) - c.t.recordFailure() - return nil, err - } - - resp, err := rt.RoundTrip(req) + resp, err := c.rt.RoundTrip(req) if err != nil { c.t.recordFailure() - slog.WarnContext(c.ctx, "dnstt roundtripper failed", + slog.WarnContext(req.Context(), "dnstt roundtripper failed", "domain", c.t.domain, "resolver", c.t.resolver, "error", err) return nil, err } - slog.DebugContext(c.ctx, "dnstt roundtripper succeeded", + slog.DebugContext(req.Context(), "dnstt roundtripper succeeded", "domain", c.t.domain, "resolver", c.t.resolver) c.t.markSucceeded() return resp, nil From 4dc4f766e229f51aa3f5f5a164d5e59888c9a930 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:14:07 -0300 Subject: [PATCH 13/14] kindling/dnstt: close tunnel after NewRoundTripper failure When a tunnel fails to create a round tripper, the DNSTT instance's goroutines (KCP poll loop, DNS senders) would keep running since the tunnel was consumed from the channel but never closed. This leaked resources and shrunk the available tunnel pool until the next probe cycle. Now closed promptly. Also add blank line separator between immutable fields (domain, resolver) and mutex-guarded fields (lastSucceeded, consecutiveFailures) for clarity. --- kindling/dnstt/parser.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index 22a5ff55..eef6c65e 100644 --- a/kindling/dnstt/parser.go +++ b/kindling/dnstt/parser.go @@ -413,6 +413,7 @@ type dnsTunnel struct { dnstt.DNSTT domain string resolver string + lastSucceeded time.Time consecutiveFailures int mx sync.RWMutex @@ -458,6 +459,7 @@ func (m *multipleDNSTTTransport) NewRoundTripper(ctx context.Context, addr strin rt, err := tun.NewRoundTripper(ctx, addr) if err != nil { tun.recordFailure() + tun.Close() slog.WarnContext(ctx, "dnstt roundtripper creation failed during connect", "domain", tun.domain, "resolver", tun.resolver, "error", err) continue From 1eadc36b7c0737388726e9af3f26015c41e22a89 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Tue, 2 Jun 2026 17:11:14 -0300 Subject: [PATCH 14/14] fix: commenting go.mod replaces --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 8500a884..ef0ad043 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ replace github.com/tetratelabs/wazero => github.com/getlantern/wazero v1.11.0-wa replace github.com/refraction-networking/water => github.com/getlantern/water v0.7.1-alpha.0.20260309190745-bd547c14b4aa -replace github.com/getlantern/dnstt => ../dnstt +// replace github.com/getlantern/dnstt => ../dnstt -replace github.com/getlantern/kindling => ../kindling +// replace github.com/getlantern/kindling => ../kindling // replace github.com/getlantern/common => ../common diff --git a/go.sum b/go.sum index 8ea8ce30..566ba493 100644 --- a/go.sum +++ b/go.sum @@ -232,6 +232,8 @@ github.com/getlantern/common v1.2.1-0.20260326210434-cb69537aaf46 h1:Ab2esudqgFz github.com/getlantern/common v1.2.1-0.20260326210434-cb69537aaf46/go.mod h1:eSSuV4bMPgQJnczBw+KWWqWNo1itzmVxC++qUBPRTt0= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= +github.com/getlantern/dnstt v0.0.0-20260511153124-dec6f436f3e5 h1:h0x2koyqXkSfOO8FbnWrGC89HE+Y3Tt2Mogavs7x6iI= +github.com/getlantern/dnstt v0.0.0-20260511153124-dec6f436f3e5/go.mod h1:eqFsaq+WT5Q6NQGP24A94RuBhIZ8+wAcbBo7Ukqf3Ms= github.com/getlantern/domainfront v0.0.0-20260419161617-0bff0b2169f4 h1:/Q9FJvKPyuXfH6tfA+C+t9/AbvGWs3Yp9iqI74FYvb4= github.com/getlantern/domainfront v0.0.0-20260419161617-0bff0b2169f4/go.mod h1:nsdIvgenGUqPKnRFjkssbfxnV/WYWyC0c/t15qGym/A= github.com/getlantern/errors v1.0.4 h1:i2iR1M9GKj4WuingpNqJ+XQEw6i6dnAgKAmLj6ZB3X0= @@ -244,6 +246,8 @@ github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 h1:cSrD9ryDfTV2y github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770/go.mod h1:GOQsoDnEHl6ZmNIL+5uVo+JWRFWozMEp18Izcb++H+A= github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694 h1:iLWm6S/47Hfk7FjW6yaD+1h6kO7C/iauV0DkVia/bXU= github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694/go.mod h1:ag5g9aWUw2FJcX5RVRpJ9EBQBy5yJuy2WXDouIn/m4w= +github.com/getlantern/kindling v0.0.0-20260529141244-21f8b144afab h1:PitYhTvo3oHRKYl4pVAoOIN8bhM+Bw+JBWncMglvHSg= +github.com/getlantern/kindling v0.0.0-20260529141244-21f8b144afab/go.mod h1:TGTxpoNVwc8Be4qkBNtf5oj2psJaEIZEq47GOPS7zkA= github.com/getlantern/lantern-box v0.0.86 h1:myJa+Crg/oMgqSFhX7DOox4XcVIx8VFiPnkel8x8YT4= github.com/getlantern/lantern-box v0.0.86/go.mod h1:BVXPyEicSu7m4nQY1OHPkOZNj87M7sYrzmY9AgyiPkc= github.com/getlantern/lantern-water v0.0.0-20260520145825-958775d51395 h1:grfGavAUp2E9w9ZoJuM3FyWyQ0sCJ64V4ZMKtZKRqTc=