Skip to content

Commit aafb4e0

Browse files
committed
proxy: imp code
1 parent c3db3d0 commit aafb4e0

File tree

8 files changed

+152
-91
lines changed

8 files changed

+152
-91
lines changed

proxy/proxy.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,11 @@ type Proxy struct {
179179
udpOOBSize int
180180

181181
// bindRetryNum is the number of retries for binding to an address for
182-
// listening.
182+
// listening. Zero means one attempt and no retries.
183183
bindRetryNum uint
184184

185-
// bindRetryIvl is the interval between retries to bind to an address for
185+
// bindRetryIvl is the interval between attempts to bind to an address for
186+
// listening.
186187
bindRetryIvl time.Duration
187188

188189
// counter counts message contexts created with [Proxy.newDNSContext].
@@ -279,7 +280,7 @@ func New(c *Config) (p *Proxy, err error) {
279280
}
280281

281282
if bindRetries := c.BindRetryConfig; bindRetries != nil && bindRetries.Enabled {
282-
p.bindRetryNum = bindRetries.Limit
283+
p.bindRetryNum = bindRetries.Count
283284
p.bindRetryIvl = bindRetries.Interval
284285
}
285286

proxy/retry.go

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package proxy
22

33
import (
4-
"fmt"
4+
"context"
55
"time"
66

7-
"github.com/AdguardTeam/golibs/errors"
7+
"github.com/AdguardTeam/golibs/logutil/slogutil"
88
)
99

1010
// BindRetryConfig contains configuration for the listeners binding retry
@@ -13,42 +13,35 @@ type BindRetryConfig struct {
1313
// Enabled indicates whether the binding should be retried.
1414
Enabled bool
1515

16-
// Limit is the maximum number of retries, which don't include the first
17-
// attempt.
18-
Limit uint
16+
// Count is the maximum number of retries after the first attempt.
17+
Count uint
1918

20-
// Interval is the minimum time to wait between retries. It must not be
21-
// negative if retrying is enabled.
19+
// Interval is the minimum time to wait after the latest failure. It must
20+
// not be negative if Enabled is true.
2221
Interval time.Duration
2322
}
2423

25-
// withRetry calls f until it returns no error or the retries limit is reached,
26-
// sleeping for ivl between attempts. retries is the number of attempts after
27-
// the first one.
28-
func withRetry[T any](
29-
f func() (res T, err error),
30-
ivl time.Duration,
31-
retries uint,
32-
) (res T, err error) {
33-
res, err = f()
24+
// bindWithRetry calls f until it returns no error or the retries limit is
25+
// reached, sleeping for configured interval between attempts. bindFunc should
26+
// 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()
3429
if err == nil {
35-
return res, nil
30+
return nil
3631
}
3732

38-
errs := []error{
39-
fmt.Errorf("attempt 1: %w", err),
40-
}
33+
p.logger.WarnContext(ctx, "binding", "attempt", 1, slogutil.KeyError, err)
4134

42-
for attempt := uint(1); attempt <= retries; attempt++ {
43-
time.Sleep(ivl)
35+
for attempt := uint(1); attempt <= p.bindRetryNum; attempt++ {
36+
time.Sleep(p.bindRetryIvl)
4437

45-
res, err = f()
46-
if err == nil {
47-
return res, nil
38+
retryErr := bindFunc()
39+
if retryErr == nil {
40+
return nil
4841
}
4942

50-
errs = append(errs, fmt.Errorf("attempt %d: %w", attempt+1, err))
43+
p.logger.WarnContext(ctx, "binding", "attempt", attempt+1, slogutil.KeyError, retryErr)
5144
}
5245

53-
return res, errors.Join(errs...)
46+
return err
5447
}

proxy/retry_internal_test.go

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,71 +4,87 @@ import (
44
"testing"
55

66
"github.com/AdguardTeam/golibs/errors"
7+
"github.com/AdguardTeam/golibs/logutil/slogutil"
78
"github.com/AdguardTeam/golibs/testutil"
89
"github.com/stretchr/testify/assert"
910
)
1011

1112
func TestWithRetry(t *testing.T) {
1213
t.Parallel()
1314

14-
const goodRes = "good"
15-
1615
const (
1716
errA errors.Error = "error about a"
1817
errB errors.Error = "error about b"
1918
)
2019

2120
var (
22-
good = func() (res any, err error) {
23-
return goodRes, nil
21+
good = func() (err error) {
22+
return nil
2423
}
2524

26-
badOne = func() (res any, err error) {
27-
return nil, errA
25+
badOne = func() (err error) {
26+
return errA
2827
}
2928

3029
returnedA = false
31-
badBoth = func() (res any, err error) {
30+
badBoth = func() (err error) {
3231
if !returnedA {
3332
returnedA = true
34-
return nil, errA
33+
34+
return errA
3535
}
3636

37-
return nil, errB
37+
return errB
38+
}
39+
40+
returnedErr = false
41+
badThenOk = func() (err error) {
42+
if !returnedErr {
43+
returnedErr = true
44+
45+
return assert.AnError
46+
}
47+
48+
return nil
3849
}
3950
)
4051

4152
testCases := []struct {
42-
name string
43-
f func() (res any, err error)
44-
want any
45-
wantErrMsg string
53+
f func() (err error)
54+
wantErr error
55+
name string
4656
}{{
47-
name: "no_error",
48-
f: good,
49-
want: goodRes,
50-
wantErrMsg: "",
57+
f: good,
58+
wantErr: nil,
59+
name: "no_error",
60+
}, {
61+
f: badOne,
62+
wantErr: errA,
63+
name: "one_error",
5164
}, {
52-
name: "one_error",
53-
f: badOne,
54-
want: nil,
55-
wantErrMsg: "attempt 1: error about a\n" +
56-
"attempt 2: error about a",
65+
f: badBoth,
66+
wantErr: errA,
67+
name: "two_errors",
5768
}, {
58-
name: "two_errors",
59-
f: badBoth,
60-
want: nil,
61-
wantErrMsg: "attempt 1: error about a\n" +
62-
"attempt 2: error about b",
69+
f: badThenOk,
70+
wantErr: nil,
71+
name: "error_then_ok",
6372
}}
6473

74+
p := &Proxy{
75+
logger: slogutil.NewDiscardLogger(),
76+
bindRetryNum: 1,
77+
bindRetryIvl: 0,
78+
}
79+
6580
for _, tc := range testCases {
6681
t.Run(tc.name, func(t *testing.T) {
6782
t.Parallel()
6883

69-
got, err := withRetry(tc.f, 0, 1)
70-
assert.Equal(t, tc.want, got)
71-
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
84+
ctx := testutil.ContextWithTimeout(t, testTimeout)
85+
86+
err := p.bindWithRetry(ctx, tc.f)
87+
assert.ErrorIs(t, err, tc.wantErr)
7288
})
7389
}
7490
}

proxy/server_dnscrypt.go

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,41 +28,79 @@ func (p *Proxy) initDNSCryptListeners(ctx context.Context) (err error) {
2828
ProviderName: p.DNSCryptProviderName,
2929
ResolverCert: p.DNSCryptResolverCert,
3030
Handler: &dnsCryptHandler{
31-
proxy: p,
32-
31+
proxy: p,
3332
reqSema: p.requestsSema,
3433
},
3534
}
3635

37-
for _, a := range p.DNSCryptUDPListenAddr {
38-
p.logger.InfoContext(ctx, "creating dnscrypt udp listener")
39-
udp, lErr := withRetry(func() (conn *net.UDPConn, err error) {
40-
return net.ListenUDP(bootstrap.NetworkUDP, a)
41-
}, p.bindRetryIvl, p.bindRetryNum)
36+
for _, addr := range p.DNSCryptUDPListenAddr {
37+
udp, lErr := p.listenDNSCryptUDP(ctx, addr)
4238
if lErr != nil {
43-
return fmt.Errorf("listening to dnscrypt udp socket: %w", lErr)
39+
return fmt.Errorf("listening to dnscrypt udp on addr %s: %w", addr, lErr)
4440
}
4541

4642
p.dnsCryptUDPListen = append(p.dnsCryptUDPListen, udp)
47-
p.logger.InfoContext(ctx, "listening for dnscrypt messages on udp", "addr", udp.LocalAddr())
4843
}
4944

50-
for _, a := range p.DNSCryptTCPListenAddr {
51-
p.logger.InfoContext(ctx, "creating a dnscrypt tcp listener")
52-
tcp, lErr := withRetry(func() (conn *net.TCPListener, err error) {
53-
return net.ListenTCP(bootstrap.NetworkTCP, a)
54-
}, p.bindRetryIvl, p.bindRetryNum)
45+
for _, addr := range p.DNSCryptTCPListenAddr {
46+
tcp, lErr := p.listenDNSCryptTCP(ctx, addr)
5547
if lErr != nil {
56-
return fmt.Errorf("listening to dnscrypt tcp socket: %w", lErr)
48+
return fmt.Errorf("listening to dnscrypt tcp on addr %s: %w", addr, lErr)
5749
}
5850

5951
p.dnsCryptTCPListen = append(p.dnsCryptTCPListen, tcp)
60-
p.logger.InfoContext(ctx, "listening for dnscrypt messages on tcp", "addr", tcp.Addr())
6152
}
6253

6354
return nil
6455
}
6556

57+
// listenDNSCryptUDP returns a new UDP connection for DNSCrypt listening on
58+
// addr.
59+
func (p *Proxy) listenDNSCryptUDP(
60+
ctx context.Context,
61+
addr *net.UDPAddr,
62+
) (conn *net.UDPConn, err error) {
63+
addrStr := addr.String()
64+
p.logger.InfoContext(ctx, "creating dnscrypt udp server socket", "addr", addrStr)
65+
66+
var udp *net.UDPConn
67+
lErr := p.bindWithRetry(ctx, func() (err error) {
68+
udp, err = net.ListenUDP(bootstrap.NetworkUDP, addr)
69+
70+
return err
71+
})
72+
if lErr != nil {
73+
return nil, fmt.Errorf("listening to udp socket: %w", lErr)
74+
}
75+
76+
p.logger.InfoContext(ctx, "listening for dnscrypt messages on udp", "addr", udp.LocalAddr())
77+
78+
return udp, nil
79+
}
80+
81+
// listenDNSCryptTCP returns a new TCP listener for DNSCrypt listening on addr.
82+
func (p *Proxy) listenDNSCryptTCP(
83+
ctx context.Context,
84+
addr *net.TCPAddr,
85+
) (conn *net.TCPListener, err error) {
86+
addrStr := addr.String()
87+
p.logger.InfoContext(ctx, "creating dnscrypt tcp server socket", "addr", addrStr)
88+
89+
var tcp *net.TCPListener
90+
lErr := p.bindWithRetry(ctx, func() (err error) {
91+
tcp, err = net.ListenTCP(bootstrap.NetworkTCP, addr)
92+
93+
return err
94+
})
95+
if lErr != nil {
96+
return nil, fmt.Errorf("listening to tcp socket: %w", lErr)
97+
}
98+
99+
p.logger.InfoContext(ctx, "listening for dnscrypt messages on tcp", "addr", tcp.Addr())
100+
101+
return tcp, nil
102+
}
103+
66104
// dnsCryptHandler - dnscrypt.Handler implementation
67105
type dnsCryptHandler struct {
68106
proxy *Proxy

proxy/server_https.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ func (p *Proxy) listenHTTP(
2929
ctx context.Context,
3030
addr *net.TCPAddr,
3131
) (ln net.Listener, tcpAddr *net.TCPAddr, err error) {
32-
tcpListen, err := withRetry(func() (conn *net.TCPListener, err error) {
33-
return net.ListenTCP(bootstrap.NetworkTCP, addr)
34-
}, p.bindRetryIvl, p.bindRetryNum)
32+
var tcpListen *net.TCPListener
33+
err = p.bindWithRetry(ctx, func() (listenErr error) {
34+
tcpListen, listenErr = net.ListenTCP(bootstrap.NetworkTCP, addr)
35+
36+
return listenErr
37+
})
3538
if err != nil {
3639
return nil, nil, fmt.Errorf("tcp listener: %w", err)
3740
}

proxy/server_quic.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,11 @@ func (p *Proxy) listenQUIC(
8989
) (conn *net.UDPConn, l *quic.EarlyListener, tr *quic.Transport, err error) {
9090
p.logger.InfoContext(ctx, "creating quic listener", "addr", addr)
9191

92-
conn, err = withRetry(func() (conn *net.UDPConn, err error) {
93-
return net.ListenUDP(bootstrap.NetworkUDP, addr)
94-
}, p.bindRetryIvl, p.bindRetryNum)
92+
err = p.bindWithRetry(ctx, func() (listenErr error) {
93+
conn, listenErr = net.ListenUDP(bootstrap.NetworkUDP, addr)
94+
95+
return listenErr
96+
})
9597
if err != nil {
9698
return nil, nil, nil, fmt.Errorf("listening to udp socket: %w", err)
9799
}

proxy/server_tcp.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@ func (p *Proxy) listenTCP(ctx context.Context, addr *net.TCPAddr) (ln *net.TCPLi
4040

4141
conf := proxynetutil.ListenConfig(p.logger)
4242

43-
listener, err := withRetry(func() (conn net.Listener, err error) {
44-
return conf.Listen(ctx, bootstrap.NetworkTCP, addrStr)
45-
}, p.bindRetryIvl, p.bindRetryNum)
43+
var listener net.Listener
44+
err = p.bindWithRetry(ctx, func() (listenErr error) {
45+
listener, listenErr = conf.Listen(ctx, bootstrap.NetworkTCP, addrStr)
46+
47+
return listenErr
48+
})
4649
if err != nil {
4750
return nil, fmt.Errorf("listening to tcp socket: %w", err)
4851
}
@@ -66,9 +69,11 @@ func (p *Proxy) initTLSListeners(ctx context.Context) (err error) {
6669
p.logger.InfoContext(ctx, "creating tls server socket", "addr", addr)
6770

6871
var tcpListen *net.TCPListener
69-
tcpListen, err = withRetry(func() (conn *net.TCPListener, err error) {
70-
return net.ListenTCP("tcp", addr)
71-
}, p.bindRetryIvl, p.bindRetryNum)
72+
err = p.bindWithRetry(ctx, func() (listenErr error) {
73+
tcpListen, listenErr = net.ListenTCP("tcp", addr)
74+
75+
return listenErr
76+
})
7277
if err != nil {
7378
return fmt.Errorf("listening on tls addr %s: %w", addr, err)
7479
}

proxy/server_udp.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,12 @@ func (p *Proxy) listenUDP(ctx context.Context, addr *net.UDPAddr) (conn *net.UDP
3838

3939
conf := proxynetutil.ListenConfig(p.logger)
4040

41-
packetConn, err := withRetry(func() (conn net.PacketConn, err error) {
42-
return conf.ListenPacket(ctx, bootstrap.NetworkUDP, addrStr)
43-
}, p.bindRetryIvl, p.bindRetryNum)
41+
var packetConn net.PacketConn
42+
err = p.bindWithRetry(ctx, func() (listenErr error) {
43+
packetConn, listenErr = conf.ListenPacket(ctx, bootstrap.NetworkUDP, addrStr)
44+
45+
return listenErr
46+
})
4447
if err != nil {
4548
return nil, fmt.Errorf("listening to udp socket: %w", err)
4649
}

0 commit comments

Comments
 (0)