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" diff --git a/go.mod b/go.mod index 736b9ab9..b319bd51 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,10 @@ 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/kindling => ../kindling + // replace github.com/getlantern/common => ../common // replace github.com/sagernet/sing => ../sing @@ -28,7 +32,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-20260529141244-21f8b144afab @@ -48,11 +52,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 +218,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 +290,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 9662f714..84dd32b8 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= @@ -800,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= @@ -853,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= @@ -889,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= @@ -921,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= @@ -937,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= @@ -980,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= @@ -992,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= @@ -1008,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= @@ -1042,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= @@ -1054,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= @@ -1083,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= @@ -1097,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= diff --git a/kindling/client.go b/kindling/client.go index 73fabf73..294b3aa2 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, @@ -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...) } diff --git a/kindling/dnstt/parser.go b/kindling/dnstt/parser.go index e9690a0c..eef6c65e 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" @@ -30,6 +31,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 +86,11 @@ 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), + tunChan: make(chan *dnsTunnel, 400), stopChan: make(chan struct{}), - options: tunnels, + probeCh: make(chan struct{}, 1), + configs: options, } m.crawlOnce.Do(func() { go func() { @@ -201,6 +196,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 != "" { @@ -219,6 +224,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) @@ -247,183 +257,240 @@ func parseDNSTTConfigs(gzipyml []byte) ([]dnsttConfig, error) { return cfgs, nil } -var waitFor = 30 * time.Second +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() } } } 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. + resolver := cfgResolver(cfg) + dnstImpl, err := newDNSTT(cfg) if err != nil { - slog.Debug("failed to create round tripper", slog.Any("error", err)) - dnst.markFailed() - go dnst.Close() + 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, domain: cfg.Domain, resolver: resolver} - client := &http.Client{ - Transport: rt, - Timeout: 15 * time.Second, + rt, err := tun.NewRoundTripper(pondCtx, "") + if err != nil { + slog.Debug("failed to create round tripper", slog.String("domain", cfg.Domain), slog.String("resolver", resolver), slog.Any("error", err)) + tun.Close() + return } - req, err := http.NewRequestWithContext(pondCtx, http.MethodGet, "https://www.gstatic.com/generate_204", http.NoBody) + // 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, "http://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() + 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)) - dnst.markFailed() - go dnst.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)) - dnst.markFailed() - go dnst.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") - dnst.markFailed() - go dnst.Close() + tun.Close() return } - // Successful tunnel - slog.Debug("adding successful tun to channel") - dnst.markSucceeded() - if !m.closed.Load() { - m.tunChan <- dnst - } + 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) } +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 +} + +// 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 stopChan chan struct{} stopChanOnce sync.Once closed atomic.Bool - options []*dnsTunnel + 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. + probeCancelFn context.CancelFunc + 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 - // lastSucceeded: the most recent time at which this DNS tunnel succeeded - lastSucceeded time.Time - mx sync.RWMutex + domain string + resolver string + + lastSucceeded time.Time + consecutiveFailures int + mx sync.RWMutex } 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{} + } } 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. -// 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 { 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() { + tun.Close() + continue + } + 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 } - - // Add the tun back to the channel. m.tunChan <- tun - return &connectedRoundtripper{t: tun, ctx: ctx, addr: addr}, nil + return &connectedRoundtripper{t: tun, rt: rt}, nil } } - return nil, fmt.Errorf("could not connect to any dns tunnel") + select { + case m.probeCh <- struct{}{}: + default: + } + 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.DebugContext(c.ctx, "failed to create dnstt round tripper", slog.Any("error", err)) - c.t.markFailed() - return nil, err - } - - resp, err := rt.RoundTrip(req) + resp, err := c.rt.RoundTrip(req) if err != nil { - slog.WarnContext(c.ctx, "dnstt roundtripper failed", slog.Any("error", err)) - c.t.markFailed() + c.t.recordFailure() + slog.WarnContext(req.Context(), "dnstt roundtripper failed", + "domain", c.t.domain, "resolver", c.t.resolver, "error", err) return nil, err } + slog.DebugContext(req.Context(), "dnstt roundtripper succeeded", + "domain", c.t.domain, "resolver", c.t.resolver) c.t.markSucceeded() return resp, nil } @@ -434,12 +501,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 failed instances synchronously, 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()) }) }