-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathconfig.go
More file actions
305 lines (259 loc) · 7.71 KB
/
config.go
File metadata and controls
305 lines (259 loc) · 7.71 KB
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
package connet
import (
"crypto/rand"
"crypto/x509"
"errors"
"fmt"
"io"
"log/slog"
"net"
"os"
"path/filepath"
"strings"
"time"
"github.com/connet-dev/connet/pkg/nat"
"github.com/quic-go/quic-go"
)
type config struct {
token string
metadata string
controlAddr *net.UDPAddr
controlHost string
controlCAs *x509.CertPool
directAddr *net.UDPAddr
directResetKey *quic.StatelessResetKey
natPMP nat.PMPConfig
handshakeIdleTimeout time.Duration
logger *slog.Logger
}
func newConfig(opts []Option) (*config, error) {
cfg := &config{
natPMP: nat.PMPConfig{
LocalResolver: nat.LocalIPSystemResolver(),
GatewayResolver: nat.GatewayIPSystemResolver(),
},
logger: slog.Default(),
}
for _, opt := range opts {
if err := opt(cfg); err != nil {
return nil, err
}
}
if cfg.token == "" {
if err := TokenFromEnv()(cfg); err != nil {
return nil, fmt.Errorf("default token: %w", err)
}
}
if cfg.controlAddr == nil {
if err := ServerAddress("127.0.0.1:19190")(cfg); err != nil {
return nil, fmt.Errorf("default control address: %w", err)
}
}
if cfg.directAddr == nil {
if err := DirectAddress(":19192")(cfg); err != nil {
return nil, fmt.Errorf("default direct address: %w", err)
}
}
if cfg.directResetKey == nil {
if err := DirectStatelessResetKeyFromEnv()(cfg); err != nil {
return nil, fmt.Errorf("default stateless reset key: %w", err)
}
if cfg.directResetKey == nil {
cfg.logger.Warn("running without a stateless reset key")
}
}
return cfg, nil
}
// Option is a functional option to configure the client
type Option func(cfg *config) error
// Token configures which token the client will use to connect to the control server
func Token(token string) Option {
return func(cfg *config) error {
cfg.token = token
return nil
}
}
// TokenFromEnv reads from $CONNET_TOKEN and configures the client token
func TokenFromEnv() Option {
return func(cfg *config) error {
if connetToken := os.Getenv("CONNET_TOKEN"); connetToken != "" {
cfg.token = connetToken
}
return nil
}
}
func Metadata(metadata string) Option {
return func(cfg *config) error {
cfg.metadata = metadata
return nil
}
}
// ServerAddress configures the control server address
func ServerAddress(address string) Option {
return func(cfg *config) error {
if i := strings.LastIndex(address, ":"); i < 0 {
// missing :port, lets give it the default
address = fmt.Sprintf("%s:%d", address, 19190)
}
addr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return fmt.Errorf("resolve control address: %w", err)
}
cfg.controlAddr = addr
if cfg.controlHost == "" {
host, _, err := net.SplitHostPort(address)
if err != nil {
return fmt.Errorf("split control address: %w", err)
}
cfg.controlHost = host
}
return nil
}
}
// ServerCAsFile reads from a file and configures the control server CAs. Used in cases where control server is not using PKIX.
func ServerCAsFile(certFile string) Option {
return func(cfg *config) error {
casData, err := os.ReadFile(certFile)
if err != nil {
return fmt.Errorf("read server CAs: %w", err)
}
cas := x509.NewCertPool()
if !cas.AppendCertsFromPEM(casData) {
return fmt.Errorf("missing server CA certificate in %s", certFile)
}
cfg.controlCAs = cas
return nil
}
}
// ServerCAs configures the control server CAs. Used in cases where control server is not using PKIX.
func ServerCAs(cas *x509.CertPool) Option {
return func(cfg *config) error {
cfg.controlCAs = cas
return nil
}
}
// ServerName configures the server name to use in TLS when connecting to the control server
func ServerName(name string) Option {
return func(cfg *config) error {
cfg.controlHost = name
return nil
}
}
// DirectAddress configures the address on which this client will listen from peer connections
func DirectAddress(address string) Option {
return func(cfg *config) error {
addr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return fmt.Errorf("resolve direct address: %w", err)
}
cfg.directAddr = addr
return nil
}
}
// DirectStatelessResetKey configures the stateless reset key for the direct server
func DirectStatelessResetKey(key *quic.StatelessResetKey) Option {
return func(cfg *config) error {
cfg.directResetKey = key
return nil
}
}
// DirectStatelessResetKeyFile reads from a file and configures the stateless reset key for the direct server
func DirectStatelessResetKeyFile(path string) Option {
return func(cfg *config) error {
keyBytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read stateless reset key: %w", err)
}
if len(keyBytes) != 32 {
return fmt.Errorf("invalid stateless reset key len %d", len(keyBytes))
}
key := quic.StatelessResetKey(keyBytes)
cfg.directResetKey = &key
return nil
}
}
// DirectStatelessResetKeyFromEnv reads stateless reset key file from the env and configures it for the direct server
func DirectStatelessResetKeyFromEnv() Option {
return func(cfg *config) error {
var name = fmt.Sprintf("stateless-reset-%s.key",
strings.TrimPrefix(strings.ReplaceAll(cfg.directAddr.String(), ":", "-"), "-"))
var path string
if connetCacheDir := os.Getenv("CONNET_CACHE_DIR"); connetCacheDir != "" {
// Support direct override if necessary, currently used in docker
path = filepath.Join(connetCacheDir, name)
} else if cacheDir := os.Getenv("CACHE_DIRECTORY"); cacheDir != "" {
// Supports setting up the cache directory via systemd. For reference
// https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#RuntimeDirectory=
path = filepath.Join(cacheDir, name)
} else if userCacheDir, err := os.UserCacheDir(); err == nil {
// Look for XDG_CACHE_HOME, fallback to $HOME/.cache
switch _, err := os.Stat(userCacheDir); {
case err == nil:
// the base user cache directory exists, continue
case errors.Is(err, os.ErrNotExist):
return nil
default:
return fmt.Errorf("stat cache dir: %w", err)
}
dir := filepath.Join(userCacheDir, "connet")
switch _, err := os.Stat(dir); {
case err == nil:
// the directory is already there, nothing to do
case errors.Is(err, os.ErrNotExist):
if err := os.Mkdir(dir, 0700); err != nil {
return fmt.Errorf("mkdir cache dir: %w", err)
}
default:
return fmt.Errorf("stat cache dir: %w", err)
}
path = filepath.Join(dir, name)
} else {
return nil
}
switch _, err := os.Stat(path); {
case err == nil:
keyBytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read stateless reset key: %w", err)
}
if len(keyBytes) < 32 {
return fmt.Errorf("stateless reset key len %d", len(keyBytes))
}
key := quic.StatelessResetKey(keyBytes)
cfg.directResetKey = &key
case errors.Is(err, os.ErrNotExist):
var key quic.StatelessResetKey
if _, err := io.ReadFull(rand.Reader, key[:]); err != nil {
return fmt.Errorf("generate stateless reset key: %w", err)
}
if err := os.WriteFile(path, key[:], 0600); err != nil {
return fmt.Errorf("write stateless reset key: %w", err)
}
cfg.directResetKey = &key
default:
return fmt.Errorf("stat stateless reset key file: %w", err)
}
return nil
}
}
// NatPMPConfig configures NATPMP behavior
func NatPMPConfig(pmp nat.PMPConfig) Option {
return func(cfg *config) error {
cfg.natPMP = pmp
return nil
}
}
// HandshakeIdleTimeout configures the handshake idle timeout to use by default when connecting to control/relay/peers
func HandshakeIdleTimeout(d time.Duration) Option {
return func(cfg *config) error {
cfg.handshakeIdleTimeout = d
return nil
}
}
// Logger configures the root logger for the client
func Logger(logger *slog.Logger) Option {
return func(cfg *config) error {
cfg.logger = logger
return nil
}
}