-
Notifications
You must be signed in to change notification settings - Fork 75
/
options.go
245 lines (209 loc) · 7.95 KB
/
options.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package npp
import (
"context"
"crypto/ecdsa"
"fmt"
"net"
"time"
"github.com/sonm-io/core/insonmnia/logging"
"github.com/sonm-io/core/insonmnia/npp/relay"
"github.com/sonm-io/core/insonmnia/npp/rendezvous"
"github.com/sonm-io/core/proto"
"github.com/sonm-io/core/util/multierror"
"github.com/sonm-io/core/util/xgrpc"
"go.uber.org/zap"
)
const (
maxRelayConcurrency = 4
)
// Option is a function that configures the listener or dialer.
type Option func(o *options) error
type puncherServerFactory func(ctx context.Context) (*natPuncherSTCP, error)
type puncherServerQUICFactory func(ctx context.Context) (*natPuncherServerQUIC, error)
type puncherClientFactory func(ctx context.Context) (*natPuncherCTCP, error)
type puncherClientQUICFactory func(ctx context.Context) (*natPuncherClientQUIC, error)
type options struct {
log *zap.Logger
puncherNewServer puncherServerFactory
puncherNewClient puncherClientFactory
puncherNewServerQUIC puncherServerQUICFactory
puncherNewClientQUIC puncherClientQUICFactory
nppBacklog int
nppMinBackoffInterval time.Duration
nppMaxBackoffInterval time.Duration
relayListener *relay.Listener
relayDialer *relay.Dialer
RelayConcurrency uint8
Protocol string
}
func newOptions() *options {
return &options{
log: zap.NewNop(),
nppBacklog: 128,
nppMinBackoffInterval: 500 * time.Millisecond,
nppMaxBackoffInterval: 8000 * time.Millisecond,
RelayConcurrency: 2,
Protocol: sonm.DefaultNPPProtocol,
}
}
// WithRendezvous is an option that specifies Rendezvous client settings and
// activates NAT punching protocol.
//
// Without this option no intermediate server will be used for obtaining
// peer's endpoints and the entire connection establishment process will fall
// back to the old good plain TCP connection.
func WithRendezvous(cfg rendezvous.Config, credentials *xgrpc.TransportCredentials) Option {
return func(o *options) error {
if len(cfg.Endpoints) == 0 {
return nil
}
o.puncherNewClient = newCTCPPuncherFactory(cfg, credentials, o)
o.puncherNewServer = newSTCPPuncherFactory(cfg, credentials, o)
if credentials.TLSConfig != nil {
// Preliminary create and save UDP socket for QUIC communication.
//
// We chose the port automatically here. However, the UDP socket is
// reused for ALL connections to be able to keep NAT mapping
// unchanged. This increases successful connection establishing
// probability after the hole has been punched at least once.
//
// IPv4 restriction is required, because in case of dual-stack
// remote network with global IPv6 address NAT isn't a problem anymore.
conn, err := net.ListenPacket("udp4", "0.0.0.0:0")
if err != nil {
return err
}
o.puncherNewClientQUIC = newCQUICPuncherFactory(cfg, credentials, conn, o)
o.puncherNewServerQUIC = newSQUICPuncherFactory(cfg, credentials, conn, o)
}
return nil
}
}
func newCTCPPuncherFactory(cfg rendezvous.Config, credentials *xgrpc.TransportCredentials, options *options) puncherClientFactory {
return func(ctx context.Context) (*natPuncherCTCP, error) {
errs := multierror.NewMultiError()
for _, addr := range cfg.Endpoints {
client, err := newRendezvousClient(ctx, addr, credentials)
if err != nil {
errs = multierror.AppendUnique(errs, err)
continue
}
return newNATPuncherClientTCP(client, options.Protocol, logging.WithTrace(ctx, options.log).Sugar())
}
return nil, fmt.Errorf("failed to connect to %+v: %v", cfg.Endpoints, errs.Error())
}
}
func newSTCPPuncherFactory(cfg rendezvous.Config, credentials *xgrpc.TransportCredentials, options *options) puncherServerFactory {
return func(ctx context.Context) (*natPuncherSTCP, error) {
errs := multierror.NewMultiError()
for _, addr := range cfg.Endpoints {
client, err := newRendezvousClient(ctx, addr, credentials)
if err != nil {
errs = multierror.AppendUnique(errs, err)
continue
}
log := logging.WithTrace(ctx, options.log.With(zap.String("protocol", options.Protocol)))
return newNATPuncherServerTCP(client, options.Protocol, log.Sugar())
}
return nil, fmt.Errorf("failed to connect to %+v: %v", cfg.Endpoints, errs.Error())
}
}
func newSQUICPuncherFactory(cfg rendezvous.Config, credentials *xgrpc.TransportCredentials, conn net.PacketConn, options *options) puncherServerQUICFactory {
return func(ctx context.Context) (*natPuncherServerQUIC, error) {
errs := multierror.NewMultiError()
for _, addr := range cfg.Endpoints {
client, err := newRendezvousClientQUIC(ctx, conn, addr, credentials)
if err != nil {
errs = multierror.AppendUnique(errs, err)
continue
}
log := logging.WithTrace(ctx, options.log)
return newNATPuncherServerQUIC(client, credentials.TLSConfig, options.Protocol, log.Sugar())
}
return nil, fmt.Errorf("failed to connect to %+v: %v", cfg.Endpoints, errs.Error())
}
}
func newCQUICPuncherFactory(cfg rendezvous.Config, credentials *xgrpc.TransportCredentials, conn net.PacketConn, options *options) puncherClientQUICFactory {
return func(ctx context.Context) (*natPuncherClientQUIC, error) {
errs := multierror.NewMultiError()
for _, addr := range cfg.Endpoints {
client, err := newRendezvousClientQUIC(ctx, conn, addr, credentials)
if err != nil {
errs = multierror.AppendUnique(errs, err)
continue
}
log := logging.WithTrace(ctx, options.log)
return newNATPuncherClientQUIC(client, credentials.TLSConfig, options.Protocol, log.Sugar())
}
return nil, fmt.Errorf("failed to connect to %+v: %v", cfg.Endpoints, errs.Error())
}
}
// WithRelay is an option that activates Relay fallback on a NPP dialer and
// listener.
//
// Without this option no intermediate server will be used for relaying
// TCP.
// One or more Relay TCP addresses must be specified in "cfg.Endpoints"
// argument. Hostname resolution is performed for each of them for environments
// with dynamic DNS addition/removal. Thus, a single Relay endpoint as a
// hostname should fit the best.
// The "credentials" argument is used both for extracting the ETH address of
// a server and for request signing to ensure that the published server
// actually owns the ETH address is publishes. When dialing this argument is
// currently ignored and can be "nil".
func WithRelay(cfg relay.Config, credentials *ecdsa.PrivateKey) Option {
return func(o *options) error {
if cfg.Concurrency > maxRelayConcurrency {
return fmt.Errorf("relay concurrency %d overflows its maximum value %d", cfg.Concurrency, maxRelayConcurrency)
}
dialer := &relay.Dialer{
Addrs: cfg.Endpoints,
Log: o.log,
}
listener, err := relay.NewListener(cfg.Endpoints, credentials, o.log)
if err != nil {
return err
}
o.relayDialer = dialer
o.relayListener = listener
o.RelayConcurrency = cfg.Concurrency
return nil
}
}
// WithLogger is an option that specifies provided logger used for the internal
// logging.
// Nil value is supported and can be passed to deactivate the logging system
// entirely.
func WithLogger(log *zap.Logger) Option {
return func(o *options) error {
o.log = log
return nil
}
}
// WithNPPBacklog is an option that specifies NPP backlog size.
func WithNPPBacklog(backlog int) Option {
return func(o *options) error {
o.nppBacklog = backlog
return nil
}
}
// WithNPPBackoff is an option that specifies NPP timeouts.
func WithNPPBackoff(min, max time.Duration) Option {
return func(o *options) error {
o.nppMinBackoffInterval = min
o.nppMaxBackoffInterval = max
return nil
}
}
// WithProtocol is an option that specifies application level protocol.
//
// In case of servers it will publish itself with a connection ID "PROTOCOL://ETH_ADDRESS".
// In case of clients this option helps to distinguish whether the destination
// peer supports such protocol.
// For example this option is used for punching NAT for SSH connections.
func WithProtocol(protocol string) Option {
return func(o *options) error {
o.Protocol = protocol
return nil
}
}