Skip to content

Commit d64332f

Browse files
committed
Pull request 394: bind-retry
Updates AdguardTeam/AdGuardDNSClient#11. Squashed commit of the following: commit ce7cbf5 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Mar 18 16:10:43 2025 +0300 proxy: imp code commit cb0f42b Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Mar 17 20:16:08 2025 +0300 proxy: imp docs commit 87bb259 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Mar 17 13:55:56 2025 +0300 proxy: imp alignment commit 88e883f Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Mar 14 19:03:31 2025 +0300 proxy: upd golibs commit f4edce5 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Mar 14 18:36:31 2025 +0300 proxy: imp names, doc commit aafb4e0 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Mar 14 18:06:32 2025 +0300 proxy: imp code commit c3db3d0 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Mar 13 20:57:39 2025 +0300 proxy: add bind retry
1 parent 7ca7339 commit d64332f

15 files changed

+434
-172
lines changed

go.mod

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/AdguardTeam/dnsproxy
33
go 1.24.1
44

55
require (
6-
github.com/AdguardTeam/golibs v0.31.0
6+
github.com/AdguardTeam/golibs v0.32.6
77
github.com/ameshkov/dnscrypt/v2 v2.3.0
88
github.com/ameshkov/dnsstamps v1.0.3
99
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0
@@ -12,15 +12,17 @@ require (
1212
github.com/patrickmn/go-cache v2.1.0+incompatible
1313
github.com/quic-go/quic-go v0.48.2
1414
github.com/stretchr/testify v1.10.0
15-
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
15+
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
1616
golang.org/x/net v0.37.0
1717
golang.org/x/sys v0.31.0
18+
// TODO(e.burkov): Either find a better maintained dependency or implement
19+
// the weighted random algorithm yourself.
1820
gonum.org/v1/gonum v0.15.1
1921
gopkg.in/yaml.v3 v3.0.1
2022
)
2123

2224
require (
23-
cloud.google.com/go v0.118.3 // indirect
25+
cloud.google.com/go v0.119.0 // indirect
2426
cloud.google.com/go/ai v0.10.0 // indirect
2527
cloud.google.com/go/auth v0.15.0 // indirect
2628
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
@@ -52,6 +54,7 @@ require (
5254
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
5355
github.com/pmezard/go-difflib v1.0.0 // indirect
5456
github.com/quic-go/qpack v0.5.1 // indirect
57+
github.com/robfig/cron/v3 v3.0.1 // indirect
5558
github.com/rogpeppe/go-internal v1.14.1 // indirect
5659
github.com/securego/gosec/v2 v2.22.2 // indirect
5760
github.com/uudashr/gocognit v1.2.0 // indirect
@@ -68,15 +71,15 @@ require (
6871
golang.org/x/mod v0.24.0 // indirect
6972
golang.org/x/oauth2 v0.28.0 // indirect
7073
golang.org/x/sync v0.12.0 // indirect
71-
golang.org/x/telemetry v0.0.0-20250305155315-2a181eac97a3 // indirect
74+
golang.org/x/telemetry v0.0.0-20250310203348-fdfaad844314 // indirect
7275
golang.org/x/term v0.30.0 // indirect
7376
golang.org/x/text v0.23.0 // indirect
7477
golang.org/x/time v0.11.0 // indirect
7578
golang.org/x/tools v0.31.0 // indirect
7679
golang.org/x/vuln v1.1.4 // indirect
77-
google.golang.org/api v0.224.0 // indirect
78-
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
79-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
80+
google.golang.org/api v0.225.0 // indirect
81+
google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf // indirect
82+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect
8083
google.golang.org/grpc v1.71.0 // indirect
8184
google.golang.org/protobuf v1.36.5 // indirect
8285
honnef.co/go/tools v0.6.1 // indirect

go.sum

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
2-
cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
1+
cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE=
2+
cloud.google.com/go v0.119.0/go.mod h1:fwB8QLzTcNevxqi8dcpR+hoMIs3jBherGS9VUBDAW08=
33
cloud.google.com/go/ai v0.10.0 h1:hwj6CI6sMKubXodoJJGTy/c2T1RbbLGM6TL3QoAvzU8=
44
cloud.google.com/go/ai v0.10.0/go.mod h1:kvnt2KeHqX8+41PVeMRBETDyQAp/RFvBWGdx/aGjNMo=
55
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
@@ -10,8 +10,8 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
1010
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
1111
cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q=
1212
cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY=
13-
github.com/AdguardTeam/golibs v0.31.0 h1:Z0oPfLTLw6iZmpE58dePy2Bel0MaX+lnDwtFEE5EmIo=
14-
github.com/AdguardTeam/golibs v0.31.0/go.mod h1:wIkZ9o2UnppeW6/YD7yJB71dYbMhiuC1Fh/I2ElW7GQ=
13+
github.com/AdguardTeam/golibs v0.32.6 h1:J/s6BiDHIJa5VsumGAzgI6S1SVLOaVB3YIvFlBK79HU=
14+
github.com/AdguardTeam/golibs v0.32.6/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA=
1515
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
1616
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
1717
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
@@ -94,6 +94,8 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
9494
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
9595
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
9696
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
97+
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
98+
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
9799
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
98100
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
99101
github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g=
@@ -129,8 +131,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
129131
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
130132
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
131133
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
132-
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
133-
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
134+
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
135+
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
134136
golang.org/x/exp/typeparams v0.0.0-20250305212735-054e65f0b394 h1:VI4qDpTkfFaCXEPrbojidLgVQhj2x4nzTccG0hjaLlU=
135137
golang.org/x/exp/typeparams v0.0.0-20250305212735-054e65f0b394/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
136138
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -154,8 +156,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
154156
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
155157
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
156158
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
157-
golang.org/x/telemetry v0.0.0-20250305155315-2a181eac97a3 h1:k+pofz4/0MRETtVtItAwfDgPUvNlWrUrFw+8dtUVUa8=
158-
golang.org/x/telemetry v0.0.0-20250305155315-2a181eac97a3/go.mod h1:16eI1RtbPZAEm3u7hpIh7JM/w5AbmlDtnrdKYaREic8=
159+
golang.org/x/telemetry v0.0.0-20250310203348-fdfaad844314 h1:UY+gQAskx5vohcvUlJDKkJPt9lALCgtZs3rs8msRatU=
160+
golang.org/x/telemetry v0.0.0-20250310203348-fdfaad844314/go.mod h1:16eI1RtbPZAEm3u7hpIh7JM/w5AbmlDtnrdKYaREic8=
159161
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
160162
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
161163
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
@@ -177,12 +179,12 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
177179
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
178180
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
179181
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
180-
google.golang.org/api v0.224.0 h1:Ir4UPtDsNiwIOHdExr3fAj4xZ42QjK7uQte3lORLJwU=
181-
google.golang.org/api v0.224.0/go.mod h1:3V39my2xAGkodXy0vEqcEtkqgw2GtrFL5WuBZlCTCOQ=
182-
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
183-
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
184-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
185-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
182+
google.golang.org/api v0.225.0 h1:+4/IVqBQm0MV5S+JW3kdEGC1WtOmM2mXN1LKH1LdNlw=
183+
google.golang.org/api v0.225.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY=
184+
google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf h1:BdIVRm+fyDUn8lrZLPSlBCfM/YKDwUBYgDoLv9+DYo0=
185+
google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
186+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY=
187+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
186188
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
187189
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
188190
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=

proxy/clock.go

Lines changed: 0 additions & 21 deletions
This file was deleted.

proxy/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ type Config struct {
9797
// DNSCrypt server.
9898
DNSCryptResolverCert *dnscrypt.Cert
9999

100+
// BindRetryConfig configures the listeners binding retrying. If nil,
101+
// retries are disabled.
102+
BindRetryConfig *BindRetryConfig
103+
100104
// DNSCryptProviderName is the DNSCrypt provider name. Required for
101105
// DNSCrypt server.
102106
DNSCryptProviderName string

proxy/exchange.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (p *Proxy) exchangeUpstreams(
3434

3535
if len(ups) == 1 {
3636
u = ups[0]
37-
resp, _, err = p.exchange(u, req, p.time)
37+
resp, _, err = p.exchange(u, req)
3838
if err != nil {
3939
return nil, nil, err
4040
}
@@ -50,7 +50,7 @@ func (p *Proxy) exchangeUpstreams(
5050
u = ups[i]
5151

5252
var elapsed time.Duration
53-
resp, elapsed, err = p.exchange(u, req, p.time)
53+
resp, elapsed, err = p.exchange(u, req)
5454
if err == nil {
5555
p.updateRTT(u.Address(), elapsed)
5656

@@ -75,13 +75,12 @@ func (p *Proxy) exchangeUpstreams(
7575
func (p *Proxy) exchange(
7676
u upstream.Upstream,
7777
req *dns.Msg,
78-
c clock,
7978
) (resp *dns.Msg, dur time.Duration, err error) {
80-
startTime := c.Now()
79+
startTime := p.time.Now()
8180
resp, err = u.Exchange(req)
8281

8382
// Don't use [time.Since] because it uses [time.Now].
84-
dur = c.Now().Sub(startTime)
83+
dur = p.time.Now().Sub(startTime)
8584

8685
addr := u.Address()
8786
q := &req.Question[0]

proxy/exchange_internal_test.go

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,15 @@ import (
1010
"github.com/AdguardTeam/dnsproxy/upstream"
1111
"github.com/AdguardTeam/golibs/logutil/slogutil"
1212
"github.com/AdguardTeam/golibs/netutil"
13+
"github.com/AdguardTeam/golibs/testutil/faketime"
14+
"github.com/AdguardTeam/golibs/timeutil"
1315
"github.com/miekg/dns"
1416
"github.com/stretchr/testify/assert"
17+
18+
//lint:ignore SA1019 See TODO for the gonum.org/v1/gonum import in go.mod.
1519
"golang.org/x/exp/rand"
1620
)
1721

18-
// fakeClock is the function-based implementation of the [clock] interface.
19-
type fakeClock struct {
20-
onNow func() (now time.Time)
21-
}
22-
23-
// type check
24-
var _ clock = (*fakeClock)(nil)
25-
26-
// Now implements the [clock] interface for *fakeClock.
27-
func (c *fakeClock) Now() (now time.Time) { return c.onNow() }
28-
2922
// newUpstreamWithErrorRate returns an [upstream.Upstream] that responds with an
3023
// error every [rate] requests. The returned upstream isn't safe for concurrent
3124
// use.
@@ -80,15 +73,15 @@ func TestProxy_Exchange_loadBalance(t *testing.T) {
8073
// value until currentNow is modified elsewhere.
8174
zeroTime := time.Unix(0, 0)
8275
currentNow := zeroTime
83-
zeroingClock := &fakeClock{
84-
onNow: func() (now time.Time) {
76+
zeroingClock := &faketime.Clock{
77+
OnNow: func() (now time.Time) {
8578
now, currentNow = currentNow, zeroTime
8679

8780
return now
8881
},
8982
}
90-
constClock := &fakeClock{
91-
onNow: func() (now time.Time) {
83+
constClock := &faketime.Clock{
84+
OnNow: func() (now time.Time) {
9285
now, currentNow = currentNow, currentNow.Add(testRTT/50)
9386

9487
return now
@@ -153,7 +146,7 @@ func TestProxy_Exchange_loadBalance(t *testing.T) {
153146

154147
testCases := []struct {
155148
wantStat map[string]int64
156-
clock clock
149+
clock timeutil.Clock
157150
name string
158151
servers []upstream.Upstream
159152
}{{

proxy/proxy.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ import (
2525
"github.com/AdguardTeam/golibs/netutil"
2626
"github.com/AdguardTeam/golibs/service"
2727
"github.com/AdguardTeam/golibs/syncutil"
28+
"github.com/AdguardTeam/golibs/timeutil"
2829
"github.com/ameshkov/dnscrypt/v2"
2930
"github.com/miekg/dns"
3031
gocache "github.com/patrickmn/go-cache"
3132
"github.com/quic-go/quic-go"
3233
"github.com/quic-go/quic-go/http3"
34+
35+
//lint:ignore SA1019 See TODO for the gonum.org/v1/gonum import in go.mod.
3336
"golang.org/x/exp/rand"
3437
)
3538

@@ -77,7 +80,7 @@ type Proxy struct {
7780
// time provides the current time.
7881
//
7982
// TODO(e.burkov): Consider configuring it.
80-
time clock
83+
time timeutil.Clock
8184

8285
// randSrc provides the source of randomness.
8386
//
@@ -178,6 +181,14 @@ type Proxy struct {
178181
// udpOOBSize is the size of the out-of-band data for UDP connections.
179182
udpOOBSize int
180183

184+
// bindRetryNum is the number of retries for binding to an address for
185+
// listening. Zero means one attempt and no retries.
186+
bindRetryNum uint
187+
188+
// bindRetryIvl is the interval between attempts to bind to an address for
189+
// listening.
190+
bindRetryIvl time.Duration
191+
181192
// counter counts message contexts created with [Proxy.newDNSContext].
182193
counter atomic.Uint64
183194

@@ -226,7 +237,7 @@ func New(c *Config) (p *Proxy, err error) {
226237
},
227238
},
228239
udpOOBSize: proxynetutil.UDPGetOOBSize(),
229-
time: realClock{},
240+
time: timeutil.SystemClock{},
230241
messages: cmp.Or[MessageConstructor](
231242
c.MessageConstructor,
232243
dnsmsg.DefaultMessageConstructor{},
@@ -263,15 +274,19 @@ func New(c *Config) (p *Proxy, err error) {
263274
p.requestsSema = syncutil.EmptySemaphore{}
264275
}
265276

266-
if p.UpstreamMode == "" {
267-
p.UpstreamMode = UpstreamModeLoadBalance
268-
} else if p.UpstreamMode == UpstreamModeFastestAddr {
277+
p.UpstreamMode = cmp.Or(p.UpstreamMode, UpstreamModeLoadBalance)
278+
if p.UpstreamMode == UpstreamModeFastestAddr {
269279
p.fastestAddr = fastip.New(&fastip.Config{
270280
Logger: p.Logger,
271281
PingWaitTimeout: p.FastestPingTimeout,
272282
})
273283
}
274284

285+
if bindRetries := c.BindRetryConfig; bindRetries != nil && bindRetries.Enabled {
286+
p.bindRetryNum = bindRetries.Count
287+
p.bindRetryIvl = bindRetries.Interval
288+
}
289+
275290
err = p.setupDNS64()
276291
if err != nil {
277292
return nil, fmt.Errorf("setting up DNS64: %w", err)
@@ -337,6 +352,14 @@ func (p *Proxy) Start(ctx context.Context) (err error) {
337352
return nil
338353
}
339354

355+
// logClose closes the closer and logs the error at the specified level if it
356+
// occurs.
357+
func (p *Proxy) logClose(ctx context.Context, l slog.Level, c io.Closer, msg string, args ...any) {
358+
if err := c.Close(); err != nil {
359+
p.logger.Log(ctx, l, msg, append(args, slogutil.KeyError, err)...)
360+
}
361+
}
362+
340363
// closeAll closes all closers and appends the occurred errors to errs.
341364
func closeAll[C io.Closer](errs []error, closers ...C) (appended []error) {
342365
for _, c := range closers {

proxy/retry.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package proxy
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/AdguardTeam/golibs/logutil/slogutil"
8+
)
9+
10+
// BindRetryConfig contains configuration for the listeners binding retry
11+
// mechanism.
12+
type BindRetryConfig struct {
13+
// Interval is the minimum time to wait after the latest failure. It must
14+
// not be negative if Enabled is true.
15+
Interval time.Duration
16+
17+
// Count is the maximum number of retries after the first attempt.
18+
Count uint
19+
20+
// Enabled indicates whether the binding should be retried.
21+
Enabled bool
22+
}
23+
24+
// bindWithRetry calls f until it returns no error or the retries limit is
25+
// reached, sleeping for configured interval between attempts. bindFunc must
26+
// not be nil and should carry the result of the binding operation itself.
27+
func (p *Proxy) bindWithRetry(ctx context.Context, bindFunc func() (err error)) (err error) {
28+
err = bindFunc()
29+
if err == nil {
30+
return nil
31+
}
32+
33+
p.logger.WarnContext(ctx, "binding", "attempt", 1, slogutil.KeyError, err)
34+
35+
for attempt := uint(1); attempt <= p.bindRetryNum; attempt++ {
36+
time.Sleep(p.bindRetryIvl)
37+
38+
retryErr := bindFunc()
39+
if retryErr == nil {
40+
return nil
41+
}
42+
43+
p.logger.WarnContext(ctx, "binding", "attempt", attempt+1, slogutil.KeyError, retryErr)
44+
}
45+
46+
return err
47+
}

0 commit comments

Comments
 (0)