Просмотр исходного кода

Move config into separate package

tags/v2.0.0-rc1
9seconds 5 лет назад
Родитель
Сommit
1a02511afe
15 измененных файлов: 615 добавлений и 552 удалений
  1. 8
    9
      cli.go
  2. 13
    13
      cli_access.go
  3. 0
    529
      config.go
  4. 151
    0
      config/config.go
  5. 47
    0
      config/type_bytes.go
  6. 46
    0
      config/type_duration.go
  7. 41
    0
      config/type_float.go
  8. 56
    0
      config/type_hostport.go
  9. 31
    0
      config/type_http_path.go
  10. 45
    0
      config/type_ip.go
  11. 42
    0
      config/type_metric_prefix.go
  12. 45
    0
      config/type_port.go
  13. 43
    0
      config/type_prefer_ip.go
  14. 45
    0
      config/type_url.go
  15. 2
    1
      utils.go

+ 8
- 9
cli.go Просмотреть файл

@@ -2,25 +2,24 @@ package main
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"os"
5
+	"io/ioutil"
6 6
 
7
+	"github.com/9seconds/mtg/v2/config"
7 8
 	"github.com/9seconds/mtg/v2/mtglib/network"
8 9
 )
9 10
 
10 11
 type cli struct {
11 12
 	network network.Network
12
-	conf    *config
13
+	conf    *config.Config
13 14
 }
14 15
 
15 16
 func (c *cli) ReadConfig(path string) error {
16
-	filefp, err := os.Open(path)
17
-	if err != nil {
18
-		return fmt.Errorf("cannot open config file: %w", err)
19
-	}
20
-
21
-	defer filefp.Close()
17
+    content, err := ioutil.ReadFile(path)
18
+    if err != nil {
19
+		return fmt.Errorf("cannot read config file: %w", err)
20
+    }
22 21
 
23
-	conf, err := parseConfig(filefp)
22
+	conf, err := config.Parse(content)
24 23
 	if err != nil {
25 24
 		return fmt.Errorf("cannot parse config: %w", err)
26 25
 	}

+ 13
- 13
cli_access.go Просмотреть файл

@@ -13,16 +13,16 @@ import (
13 13
 	"strings"
14 14
 )
15 15
 
16
-type runAccessResponse struct {
17
-	IPv4   *runAccessResponseURLs `json:"ipv4,omitempty"`
18
-	IPv6   *runAccessResponseURLs `json:"ipv6,omitempty"`
16
+type accessResponse struct {
17
+	IPv4   *accessResponseURLs `json:"ipv4,omitempty"`
18
+	IPv6   *accessResponseURLs `json:"ipv6,omitempty"`
19 19
 	Secret struct {
20 20
 		Hex    string `json:"hex"`
21 21
 		Base64 string `json:"base64"`
22 22
 	} `json:"secret"`
23 23
 }
24 24
 
25
-type runAccessResponseURLs struct {
25
+type accessResponseURLs struct {
26 26
 	IP        net.IP `json:"ip"`
27 27
 	TgURL     string `json:"tg_url"`
28 28
 	TgQrCode  string `json:"tg_qrcode"`
@@ -53,9 +53,9 @@ func (c *cliCommandAccess) Run(cli *CLI) error {
53 53
 		ipv6 = c.getIP("tcp6")
54 54
 	}
55 55
 
56
-	resp := runAccessResponse{
57
-		IPv4: c.makeResponseURLs(ipv4, cli),
58
-		IPv6: c.makeResponseURLs(ipv6, cli),
56
+	resp := accessResponse{
57
+		IPv4: c.makeURLs(ipv4, cli),
58
+		IPv6: c.makeURLs(ipv6, cli),
59 59
 	}
60 60
 	resp.Secret.Base64 = c.conf.Secret.Base64()
61 61
 	resp.Secret.Hex = c.conf.Secret.Hex()
@@ -101,7 +101,7 @@ func (c *cliCommandAccess) getIP(protocol string) net.IP {
101 101
 	return net.ParseIP(strings.TrimSpace(string(data)))
102 102
 }
103 103
 
104
-func (c *cliCommandAccess) makeResponseURLs(ip net.IP, cli *CLI) *runAccessResponseURLs {
104
+func (c *cliCommandAccess) makeURLs(ip net.IP, cli *CLI) *accessResponseURLs {
105 105
 	if ip == nil {
106 106
 		return nil
107 107
 	}
@@ -109,7 +109,7 @@ func (c *cliCommandAccess) makeResponseURLs(ip net.IP, cli *CLI) *runAccessRespo
109 109
 	values := url.Values{}
110 110
 
111 111
 	values.Set("server", ip.String())
112
-	values.Set("port", strconv.Itoa(int(c.conf.BindTo.port.Value(0))))
112
+	values.Set("port", strconv.Itoa(int(c.conf.BindTo.PortValue(0))))
113 113
 
114 114
 	if cli.Access.Hex {
115 115
 		values.Set("secret", c.conf.Secret.Hex())
@@ -119,7 +119,7 @@ func (c *cliCommandAccess) makeResponseURLs(ip net.IP, cli *CLI) *runAccessRespo
119 119
 
120 120
 	urlQuery := values.Encode()
121 121
 
122
-	rv := &runAccessResponseURLs{
122
+	rv := &accessResponseURLs{
123 123
 		IP: ip,
124 124
 		TgURL: (&url.URL{
125 125
 			Scheme:   "tg",
@@ -134,13 +134,13 @@ func (c *cliCommandAccess) makeResponseURLs(ip net.IP, cli *CLI) *runAccessRespo
134 134
 		}).String(),
135 135
 	}
136 136
 
137
-	rv.TgQrCode = c.makeResponseQRCode(rv.TgURL)
138
-	rv.TmeQrCode = c.makeResponseQRCode(rv.TmeURL)
137
+	rv.TgQrCode = c.makeQRCode(rv.TgURL)
138
+	rv.TmeQrCode = c.makeQRCode(rv.TmeURL)
139 139
 
140 140
 	return rv
141 141
 }
142 142
 
143
-func (c *cliCommandAccess) makeResponseQRCode(data string) string {
143
+func (c *cliCommandAccess) makeQRCode(data string) string {
144 144
 	values := url.Values{}
145 145
 
146 146
 	values.Set("qzone", "4")

+ 0
- 529
config.go Просмотреть файл

@@ -1,529 +0,0 @@
1
-package main
2
-
3
-import (
4
-	"bytes"
5
-	"encoding/json"
6
-	"fmt"
7
-	"io"
8
-	"net"
9
-	"net/url"
10
-	"regexp"
11
-	"strconv"
12
-	"strings"
13
-	"time"
14
-
15
-	"github.com/9seconds/mtg/v2/mtglib"
16
-	"github.com/alecthomas/units"
17
-	"github.com/pelletier/go-toml"
18
-)
19
-
20
-type configTypeHostPort struct {
21
-	host configTypeIP
22
-	port configTypePort
23
-}
24
-
25
-func (c *configTypeHostPort) UnmarshalText(data []byte) error {
26
-	if len(data) == 0 {
27
-		return nil
28
-	}
29
-
30
-	host, port, err := net.SplitHostPort(string(data))
31
-	if err != nil {
32
-		return fmt.Errorf("incorrect host:port syntax: %w", err)
33
-	}
34
-
35
-	if err := c.port.UnmarshalJSON([]byte(port)); err != nil {
36
-		return fmt.Errorf("incorrect port in host:port: %w", err)
37
-	}
38
-
39
-	if err := c.host.UnmarshalText([]byte(host)); err != nil {
40
-		return fmt.Errorf("incorrect host: %w", err)
41
-	}
42
-
43
-	return nil
44
-}
45
-
46
-func (c configTypeHostPort) MarshalText() ([]byte, error) { // nolint: unparam
47
-	return []byte(c.String()), nil
48
-}
49
-
50
-func (c configTypeHostPort) String() string {
51
-	return c.Value(net.IP{}, 0)
52
-}
53
-
54
-func (c configTypeHostPort) Value(defaultHostValue net.IP, defaultPortValue uint) string {
55
-	return net.JoinHostPort(c.host.Value(defaultHostValue).String(),
56
-		strconv.Itoa(int(c.port.Value(defaultPortValue))))
57
-}
58
-
59
-type configTypePort struct {
60
-	value uint
61
-}
62
-
63
-func (c *configTypePort) UnmarshalJSON(data []byte) error {
64
-	if len(data) == 0 {
65
-		return nil
66
-	}
67
-
68
-	intValue, err := strconv.ParseUint(string(data), 10, 16)
69
-	if err != nil {
70
-		return fmt.Errorf("port number is not a number: %w", err)
71
-	}
72
-
73
-	if intValue == 0 || intValue > 65536 {
74
-		return fmt.Errorf("port number should be 0 < portNo < 65536: %d", intValue)
75
-	}
76
-
77
-	c.value = uint(intValue)
78
-
79
-	return nil
80
-}
81
-
82
-func (c *configTypePort) MarshalJSON() ([]byte, error) {
83
-	return json.Marshal(c.value)
84
-}
85
-
86
-func (c configTypePort) String() string {
87
-	return strconv.Itoa(int(c.value))
88
-}
89
-
90
-func (c configTypePort) Value(defaultValue uint) uint {
91
-	if c.value == 0 {
92
-		return defaultValue
93
-	}
94
-
95
-	return c.value
96
-}
97
-
98
-type configTypeBytes struct {
99
-	value uint
100
-}
101
-
102
-func (c *configTypeBytes) UnmarshalText(data []byte) error {
103
-	if len(data) == 0 {
104
-		return nil
105
-	}
106
-
107
-	value, err := units.ParseStrictBytes(strings.ToUpper(string(data)))
108
-	if err != nil {
109
-		return fmt.Errorf("incorrect bytes value: %w", err)
110
-	}
111
-
112
-	if value < 0 {
113
-		return fmt.Errorf("%d should be positive number", value)
114
-	}
115
-
116
-	c.value = uint(value)
117
-
118
-	return nil
119
-}
120
-
121
-func (c configTypeBytes) MarshalText() ([]byte, error) { // nolint: unparam
122
-	return []byte(c.String()), nil
123
-}
124
-
125
-func (c configTypeBytes) String() string {
126
-	return units.ToString(int64(c.value), 1024, "ib", "b")
127
-}
128
-
129
-func (c configTypeBytes) Value(defaultValue uint) uint {
130
-	if c.value == 0 {
131
-		return defaultValue
132
-	}
133
-
134
-	return c.value
135
-}
136
-
137
-type configTypePreferIP struct {
138
-	value string
139
-}
140
-
141
-func (c *configTypePreferIP) UnmarshalText(data []byte) error {
142
-	if len(data) == 0 {
143
-		return nil
144
-	}
145
-
146
-	text := strings.ToLower(string(data))
147
-
148
-	switch text {
149
-	case "prefer-ipv4", "prefer-ipv6", "only-ipv4", "only-ipv6":
150
-		c.value = text
151
-	default:
152
-		return fmt.Errorf("incorrect prefer-ip value: %s", string(data))
153
-	}
154
-
155
-	return nil
156
-}
157
-
158
-func (c configTypePreferIP) MarshalText() ([]byte, error) { // nolint: unparam
159
-	return []byte(c.value), nil
160
-}
161
-
162
-func (c *configTypePreferIP) String() string {
163
-	return c.value
164
-}
165
-
166
-func (c *configTypePreferIP) Value(defaultValue string) string {
167
-	if c.value == "" {
168
-		return defaultValue
169
-	}
170
-
171
-	return c.value
172
-}
173
-
174
-type configTypeDuration struct {
175
-	value time.Duration
176
-}
177
-
178
-func (c *configTypeDuration) UnmarshalText(data []byte) error {
179
-	if len(data) == 0 {
180
-		return nil
181
-	}
182
-
183
-	dur, err := time.ParseDuration(strings.ToLower(string(data)))
184
-	if err != nil {
185
-		return fmt.Errorf("incorrect duration: %w", err)
186
-	}
187
-
188
-	if dur < 0 {
189
-		return fmt.Errorf("%s should be positive duration", dur)
190
-	}
191
-
192
-	c.value = dur
193
-
194
-	return nil
195
-}
196
-
197
-func (c configTypeDuration) MarshalText() ([]byte, error) { // nolint: unparam
198
-	return []byte(c.value.String()), nil
199
-}
200
-
201
-func (c configTypeDuration) String() string {
202
-	return c.value.String()
203
-}
204
-
205
-func (c configTypeDuration) Value(defaultValue time.Duration) time.Duration {
206
-	if c.value == 0 {
207
-		return defaultValue
208
-	}
209
-
210
-	return c.value
211
-}
212
-
213
-type configTypeFloat struct {
214
-	value float64
215
-}
216
-
217
-func (c *configTypeFloat) UnmarshalJSON(data []byte) error {
218
-	value, err := strconv.ParseFloat(string(data), 64)
219
-	if err != nil {
220
-		return fmt.Errorf("incorrect float value: %w", err)
221
-	}
222
-
223
-	if value < 0 {
224
-		return fmt.Errorf("%f should be positive", value)
225
-	}
226
-
227
-	c.value = value
228
-
229
-	return nil
230
-}
231
-
232
-func (c *configTypeFloat) MarshalText() ([]byte, error) { // nolint: unparam
233
-	return []byte(c.String()), nil
234
-}
235
-
236
-func (c configTypeFloat) String() string {
237
-	return strconv.FormatFloat(c.value, 'f', -1, 64)
238
-}
239
-
240
-func (c configTypeFloat) Value(defaultValue float64) float64 {
241
-	if c.value < 0.00001 {
242
-		return defaultValue
243
-	}
244
-
245
-	return c.value
246
-}
247
-
248
-type configTypeIP struct {
249
-	value net.IP
250
-}
251
-
252
-func (c *configTypeIP) UnmarshalText(data []byte) error {
253
-	if len(data) == 0 {
254
-		return nil
255
-	}
256
-
257
-	ip := net.ParseIP(string(data))
258
-	if ip == nil {
259
-		return fmt.Errorf("incorrect ip address: %s", string(data))
260
-	}
261
-
262
-	c.value = ip
263
-
264
-	return nil
265
-}
266
-
267
-func (c *configTypeIP) MarshalText() ([]byte, error) { // nolint: unparam
268
-	return []byte(c.String()), nil
269
-}
270
-
271
-func (c configTypeIP) String() string {
272
-	if c.value == nil {
273
-		return ""
274
-	}
275
-
276
-	return c.value.String()
277
-}
278
-
279
-func (c configTypeIP) Value(defaultValue net.IP) net.IP {
280
-	if c.value == nil {
281
-		return defaultValue
282
-	}
283
-
284
-	return c.value
285
-}
286
-
287
-type configTypeURL struct {
288
-	value *url.URL
289
-}
290
-
291
-func (c *configTypeURL) UnmarshalText(data []byte) error {
292
-	if len(data) == 0 {
293
-		return nil
294
-	}
295
-
296
-	value, err := url.Parse(string(data))
297
-	if err != nil {
298
-		return fmt.Errorf("incorrect URL: %w", err)
299
-	}
300
-
301
-	c.value = value
302
-
303
-	return nil
304
-}
305
-
306
-func (c *configTypeURL) MarshalText() ([]byte, error) { // nolint: unparam
307
-	return []byte(c.String()), nil
308
-}
309
-
310
-func (c configTypeURL) String() string {
311
-	if c.value == nil {
312
-		return ""
313
-	}
314
-
315
-	return c.value.String()
316
-}
317
-
318
-func (c configTypeURL) Value(defaultValue *url.URL) *url.URL {
319
-	if c.value == nil {
320
-		return defaultValue
321
-	}
322
-
323
-	return c.value
324
-}
325
-
326
-type configTypeMetricPrefix struct {
327
-	value string
328
-}
329
-
330
-func (c *configTypeMetricPrefix) UnmarshalText(data []byte) error {
331
-	if len(data) == 0 {
332
-		return nil
333
-	}
334
-
335
-	prefix := string(data)
336
-
337
-	if ok, err := regexp.MatchString("^[a-z0-9]+$", prefix); !ok || err != nil {
338
-		return fmt.Errorf("incorrect metric prefix: %s", prefix)
339
-	}
340
-
341
-	c.value = prefix
342
-
343
-	return nil
344
-}
345
-
346
-func (c configTypeMetricPrefix) MarshalText() ([]byte, error) { // nolint: unparam
347
-	return []byte(c.String()), nil
348
-}
349
-
350
-func (c configTypeMetricPrefix) String() string {
351
-	return c.value
352
-}
353
-
354
-func (c configTypeMetricPrefix) Value(defaultValue string) string {
355
-	if c.value == "" {
356
-		return defaultValue
357
-	}
358
-
359
-	return c.value
360
-}
361
-
362
-type configTypeHTTPPath struct {
363
-	value string
364
-}
365
-
366
-func (c *configTypeHTTPPath) UnmarshalText(data []byte) error { // nolint: unparam
367
-	if len(data) > 0 {
368
-		c.value = "/" + strings.Trim(string(data), "/")
369
-	}
370
-
371
-	return nil
372
-}
373
-
374
-func (c configTypeHTTPPath) MarshalText() ([]byte, error) { // nolint: unparam
375
-	return []byte(c.String()), nil
376
-}
377
-
378
-func (c configTypeHTTPPath) String() string {
379
-	return c.value
380
-}
381
-
382
-func (c configTypeHTTPPath) Value(defaultValue string) string {
383
-	if c.value == "" {
384
-		return defaultValue
385
-	}
386
-
387
-	return c.value
388
-}
389
-
390
-type config struct {
391
-	Debug     bool               `json:"debug"`
392
-	Secret    mtglib.Secret      `json:"secret"`
393
-	BindTo    configTypeHostPort `json:"bind-to"`
394
-	TCPBuffer configTypeBytes    `json:"tcp-buffer"`
395
-	PreferIP  configTypePreferIP `json:"prefer-ip"`
396
-	CloakPort configTypePort     `json:"cloak-port"`
397
-	Probes    struct {
398
-		Time struct {
399
-			Enabled       bool               `json:"enabled"`
400
-			AllowSkewness configTypeDuration `json:"allow-skewness"`
401
-		} `json:"time"`
402
-		AntiReplay struct {
403
-			Enabled   bool            `json:"enabled"`
404
-			MaxSize   configTypeBytes `json:"max-size"`
405
-			ErrorRate configTypeFloat `json:"error-rate"`
406
-		} `json:"anti-replay"`
407
-	} `json:"probes"`
408
-	Network struct {
409
-		PublicIP struct {
410
-			IPv4 configTypeIP `json:"ipv4"`
411
-			IPv6 configTypeIP `json:"ipv6"`
412
-		} `json:"public-ip"`
413
-		Timeout struct {
414
-			TCP  configTypeDuration `json:"tcp"`
415
-			Idle configTypeDuration `json:"idle"`
416
-		} `json:"timeout"`
417
-		DOHIP   configTypeIP    `json:"doh-ip"`
418
-		Proxies []configTypeURL `json:"proxies"`
419
-	} `json:"network"`
420
-	Stats struct {
421
-		StatsD struct {
422
-			Enabled      bool                   `json:"enabled"`
423
-			Address      configTypeHostPort     `json:"address"`
424
-			MetricPrefix configTypeMetricPrefix `json:"metric-prefix"`
425
-		} `json:"statsd"`
426
-		Prometheus struct {
427
-			Enabled      bool                   `json:"enabled"`
428
-			BindTo       configTypeHostPort     `json:"bind-to"`
429
-			HTTPPath     configTypeHTTPPath     `json:"http-path"`
430
-			MetricPrefix configTypeMetricPrefix `json:"metric-prefix"`
431
-		} `json:"prometheus"`
432
-	} `json:"stats"`
433
-}
434
-
435
-func (c *config) Validate() error {
436
-	if len(c.Secret.Key) == 0 || c.Secret.Host == "" {
437
-		return fmt.Errorf("incorrect secret %s", c.Secret.String())
438
-	}
439
-
440
-	return nil
441
-}
442
-
443
-func (c *config) String() string {
444
-	buf := &bytes.Buffer{}
445
-	encoder := json.NewEncoder(buf)
446
-
447
-	encoder.SetEscapeHTML(false)
448
-
449
-	if err := encoder.Encode(c); err != nil {
450
-		panic(err)
451
-	}
452
-
453
-	return buf.String()
454
-}
455
-
456
-type configRaw struct {
457
-	Debug     bool   `toml:"debug" json:"debug"`
458
-	Secret    string `toml:"secret" json:"secret"`
459
-	BindTo    string `toml:"bind-to" json:"bind-to"`
460
-	TCPBuffer string `toml:"tcp-buffer" json:"tcp-buffer"`
461
-	PreferIP  string `toml:"prefer-ip" json:"prefer-ip"`
462
-	CloakPort uint   `toml:"cloak-port" json:"cloak-port"`
463
-	Probes    struct {
464
-		Time struct {
465
-			Enabled       bool   `toml:"enabled" json:"enabled"`
466
-			AllowSkewness string `toml:"allow-skewness" json:"allow-skewness"`
467
-		} `toml:"time" json:"time"`
468
-		AntiReplay struct {
469
-			Enabled   bool    `toml:"enabled" json:"enabled"`
470
-			MaxSize   string  `toml:"max-size" json:"max-size"`
471
-			ErrorRate float64 `toml:"error-rate" json:"error-rate"`
472
-		} `toml:"anti-replay" json:"anti-replay"`
473
-	} `toml:"probes" json:"probes"`
474
-	Network struct {
475
-		PublicIP struct {
476
-			IPv4 string `toml:"ipv4" json:"ipv4"`
477
-			IPv6 string `toml:"ipv6" json:"ipv6"`
478
-		} `toml:"public-ip" json:"public-ip"`
479
-		Timeout struct {
480
-			TCP  string `toml:"tcp" json:"tcp"`
481
-			Idle string `toml:"idle" json:"idle"`
482
-		} `toml:"timeout" json:"timeout"`
483
-		DOHIP   string   `toml:"doh-ip" json:"doh-ip"`
484
-		Proxies []string `toml:"proxies" json:"proxies"`
485
-	} `toml:"network" json:"network"`
486
-	Stats struct {
487
-		StatsD struct {
488
-			Enabled      bool   `toml:"enabled" json:"enabled"`
489
-			Address      string `toml:"address" json:"address"`
490
-			MetricPrefix string `toml:"metric-prefix" json:"metric-prefix"`
491
-		} `toml:"statsd" json:"statsd"`
492
-		Prometheus struct {
493
-			Enabled      bool   `toml:"enabled" json:"enabled"`
494
-			BindTo       string `toml:"bind-to" json:"bind-to"`
495
-			HTTPPath     string `toml:"http-path" json:"http-path"`
496
-			MetricPrefix string `toml:"metric-prefix" json:"metric-prefix"`
497
-		} `toml:"prometheus" json:"prometheus"`
498
-	} `toml:"stats" json:"stats"`
499
-}
500
-
501
-func parseConfig(reader io.Reader) (*config, error) {
502
-	rawConf := &configRaw{}
503
-
504
-	if err := toml.NewDecoder(reader).Decode(rawConf); err != nil {
505
-		return nil, fmt.Errorf("cannot parse toml config: %w", err)
506
-	}
507
-
508
-	jsonBuf := &bytes.Buffer{}
509
-	jsonEncoder := json.NewEncoder(jsonBuf)
510
-
511
-	jsonEncoder.SetEscapeHTML(false)
512
-	jsonEncoder.SetIndent("", "")
513
-
514
-	if err := jsonEncoder.Encode(rawConf); err != nil {
515
-		return nil, fmt.Errorf("cannot dump into interim format: %w", err)
516
-	}
517
-
518
-	conf := &config{}
519
-
520
-	if err := json.NewDecoder(jsonBuf).Decode(conf); err != nil {
521
-		return nil, fmt.Errorf("cannot parse final config: %w", err)
522
-	}
523
-
524
-	if err := conf.Validate(); err != nil {
525
-		return nil, fmt.Errorf("cannot validate config: %w", err)
526
-	}
527
-
528
-	return conf, nil
529
-}

+ 151
- 0
config/config.go Просмотреть файл

@@ -0,0 +1,151 @@
1
+package config
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/json"
6
+	"fmt"
7
+
8
+	"github.com/9seconds/mtg/v2/mtglib"
9
+	"github.com/pelletier/go-toml"
10
+)
11
+
12
+type Config struct {
13
+	Debug     bool          `json:"debug"`
14
+	Secret    mtglib.Secret `json:"secret"`
15
+	BindTo    TypeHostPort  `json:"bind-to"`
16
+	TCPBuffer TypeBytes     `json:"tcp-buffer"`
17
+	PreferIP  TypePreferIP  `json:"prefer-ip"`
18
+	CloakPort TypePort      `json:"cloak-port"`
19
+	Probes    struct {
20
+		Time struct {
21
+			Enabled       bool         `json:"enabled"`
22
+			AllowSkewness TypeDuration `json:"allow-skewness"`
23
+		} `json:"time"`
24
+		AntiReplay struct {
25
+			Enabled   bool      `json:"enabled"`
26
+			MaxSize   TypeBytes `json:"max-size"`
27
+			ErrorRate TypeFloat `json:"error-rate"`
28
+		} `json:"anti-replay"`
29
+	} `json:"probes"`
30
+	Network struct {
31
+		PublicIP struct {
32
+			IPv4 TypeIP `json:"ipv4"`
33
+			IPv6 TypeIP `json:"ipv6"`
34
+		} `json:"public-ip"`
35
+		Timeout struct {
36
+			TCP  TypeDuration `json:"tcp"`
37
+			Idle TypeDuration `json:"idle"`
38
+		} `json:"timeout"`
39
+		DOHIP   TypeIP    `json:"doh-ip"`
40
+		Proxies []TypeURL `json:"proxies"`
41
+	} `json:"network"`
42
+	Stats struct {
43
+		StatsD struct {
44
+			Enabled      bool             `json:"enabled"`
45
+			Address      TypeHostPort     `json:"address"`
46
+			MetricPrefix TypeMetricPrefix `json:"metric-prefix"`
47
+		} `json:"statsd"`
48
+		Prometheus struct {
49
+			Enabled      bool             `json:"enabled"`
50
+			BindTo       TypeHostPort     `json:"bind-to"`
51
+			HTTPPath     TypeHTTPPath     `json:"http-path"`
52
+			MetricPrefix TypeMetricPrefix `json:"metric-prefix"`
53
+		} `json:"prometheus"`
54
+	} `json:"stats"`
55
+}
56
+
57
+func (c *Config) Validate() error {
58
+	if len(c.Secret.Key) == 0 || c.Secret.Host == "" {
59
+		return fmt.Errorf("incorrect secret %s", c.Secret.String())
60
+	}
61
+
62
+	return nil
63
+}
64
+
65
+func (c *Config) String() string {
66
+	buf := &bytes.Buffer{}
67
+	encoder := json.NewEncoder(buf)
68
+
69
+	encoder.SetEscapeHTML(false)
70
+
71
+	if err := encoder.Encode(c); err != nil {
72
+		panic(err)
73
+	}
74
+
75
+	return buf.String()
76
+}
77
+
78
+type configRaw struct {
79
+	Debug     bool   `toml:"debug" json:"debug"`
80
+	Secret    string `toml:"secret" json:"secret"`
81
+	BindTo    string `toml:"bind-to" json:"bind-to"`
82
+	TCPBuffer string `toml:"tcp-buffer" json:"tcp-buffer"`
83
+	PreferIP  string `toml:"prefer-ip" json:"prefer-ip"`
84
+	CloakPort uint   `toml:"cloak-port" json:"cloak-port"`
85
+	Probes    struct {
86
+		Time struct {
87
+			Enabled       bool   `toml:"enabled" json:"enabled"`
88
+			AllowSkewness string `toml:"allow-skewness" json:"allow-skewness"`
89
+		} `toml:"time" json:"time"`
90
+		AntiReplay struct {
91
+			Enabled   bool    `toml:"enabled" json:"enabled"`
92
+			MaxSize   string  `toml:"max-size" json:"max-size"`
93
+			ErrorRate float64 `toml:"error-rate" json:"error-rate"`
94
+		} `toml:"anti-replay" json:"anti-replay"`
95
+	} `toml:"probes" json:"probes"`
96
+	Network struct {
97
+		PublicIP struct {
98
+			IPv4 string `toml:"ipv4" json:"ipv4"`
99
+			IPv6 string `toml:"ipv6" json:"ipv6"`
100
+		} `toml:"public-ip" json:"public-ip"`
101
+		Timeout struct {
102
+			TCP  string `toml:"tcp" json:"tcp"`
103
+			Idle string `toml:"idle" json:"idle"`
104
+		} `toml:"timeout" json:"timeout"`
105
+		DOHIP   string   `toml:"doh-ip" json:"doh-ip"`
106
+		Proxies []string `toml:"proxies" json:"proxies"`
107
+	} `toml:"network" json:"network"`
108
+	Stats struct {
109
+		StatsD struct {
110
+			Enabled      bool   `toml:"enabled" json:"enabled"`
111
+			Address      string `toml:"address" json:"address"`
112
+			MetricPrefix string `toml:"metric-prefix" json:"metric-prefix"`
113
+		} `toml:"statsd" json:"statsd"`
114
+		Prometheus struct {
115
+			Enabled      bool   `toml:"enabled" json:"enabled"`
116
+			BindTo       string `toml:"bind-to" json:"bind-to"`
117
+			HTTPPath     string `toml:"http-path" json:"http-path"`
118
+			MetricPrefix string `toml:"metric-prefix" json:"metric-prefix"`
119
+		} `toml:"prometheus" json:"prometheus"`
120
+	} `toml:"stats" json:"stats"`
121
+}
122
+
123
+func Parse(rawData []byte) (*Config, error) {
124
+	rawConf := &configRaw{}
125
+
126
+	if err := toml.Unmarshal(rawData, rawConf); err != nil {
127
+		return nil, fmt.Errorf("cannot parse toml config: %w", err)
128
+	}
129
+
130
+	jsonBuf := &bytes.Buffer{}
131
+	jsonEncoder := json.NewEncoder(jsonBuf)
132
+
133
+	jsonEncoder.SetEscapeHTML(false)
134
+	jsonEncoder.SetIndent("", "")
135
+
136
+	if err := jsonEncoder.Encode(rawConf); err != nil {
137
+		return nil, fmt.Errorf("cannot dump into interim format: %w", err)
138
+	}
139
+
140
+	conf := &Config{}
141
+
142
+	if err := json.NewDecoder(jsonBuf).Decode(conf); err != nil {
143
+		return nil, fmt.Errorf("cannot parse final config: %w", err)
144
+	}
145
+
146
+	if err := conf.Validate(); err != nil {
147
+		return nil, fmt.Errorf("cannot validate config: %w", err)
148
+	}
149
+
150
+	return conf, nil
151
+}

+ 47
- 0
config/type_bytes.go Просмотреть файл

@@ -0,0 +1,47 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+
7
+	"github.com/alecthomas/units"
8
+)
9
+
10
+type TypeBytes struct {
11
+	value uint
12
+}
13
+
14
+func (c *TypeBytes) UnmarshalText(data []byte) error {
15
+	if len(data) == 0 {
16
+		return nil
17
+	}
18
+
19
+	value, err := units.ParseStrictBytes(strings.ToUpper(string(data)))
20
+	if err != nil {
21
+		return fmt.Errorf("incorrect bytes value: %w", err)
22
+	}
23
+
24
+	if value < 0 {
25
+		return fmt.Errorf("%d should be positive number", value)
26
+	}
27
+
28
+	c.value = uint(value)
29
+
30
+	return nil
31
+}
32
+
33
+func (c TypeBytes) MarshalText() ([]byte, error) { // nolint: unparam
34
+	return []byte(c.String()), nil
35
+}
36
+
37
+func (c TypeBytes) String() string {
38
+	return units.ToString(int64(c.value), 1024, "ib", "b")
39
+}
40
+
41
+func (c TypeBytes) Value(defaultValue uint) uint {
42
+	if c.value == 0 {
43
+		return defaultValue
44
+	}
45
+
46
+	return c.value
47
+}

+ 46
- 0
config/type_duration.go Просмотреть файл

@@ -0,0 +1,46 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+	"time"
7
+)
8
+
9
+type TypeDuration struct {
10
+	value time.Duration
11
+}
12
+
13
+func (c *TypeDuration) UnmarshalText(data []byte) error {
14
+	if len(data) == 0 {
15
+		return nil
16
+	}
17
+
18
+	dur, err := time.ParseDuration(strings.ToLower(string(data)))
19
+	if err != nil {
20
+		return fmt.Errorf("incorrect duration: %w", err)
21
+	}
22
+
23
+	if dur < 0 {
24
+		return fmt.Errorf("%s should be positive duration", dur)
25
+	}
26
+
27
+	c.value = dur
28
+
29
+	return nil
30
+}
31
+
32
+func (c TypeDuration) MarshalText() ([]byte, error) { // nolint: unparam
33
+	return []byte(c.value.String()), nil
34
+}
35
+
36
+func (c TypeDuration) String() string {
37
+	return c.value.String()
38
+}
39
+
40
+func (c TypeDuration) Value(defaultValue time.Duration) time.Duration {
41
+	if c.value == 0 {
42
+		return defaultValue
43
+	}
44
+
45
+	return c.value
46
+}

+ 41
- 0
config/type_float.go Просмотреть файл

@@ -0,0 +1,41 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strconv"
6
+)
7
+
8
+type TypeFloat struct {
9
+	value float64
10
+}
11
+
12
+func (c *TypeFloat) UnmarshalJSON(data []byte) error {
13
+	value, err := strconv.ParseFloat(string(data), 64)
14
+	if err != nil {
15
+		return fmt.Errorf("incorrect float value: %w", err)
16
+	}
17
+
18
+	if value < 0 {
19
+		return fmt.Errorf("%f should be positive", value)
20
+	}
21
+
22
+	c.value = value
23
+
24
+	return nil
25
+}
26
+
27
+func (c *TypeFloat) MarshalText() ([]byte, error) { // nolint: unparam
28
+	return []byte(c.String()), nil
29
+}
30
+
31
+func (c TypeFloat) String() string {
32
+	return strconv.FormatFloat(c.value, 'f', -1, 64)
33
+}
34
+
35
+func (c TypeFloat) Value(defaultValue float64) float64 {
36
+	if c.value < 0.00001 {
37
+		return defaultValue
38
+	}
39
+
40
+	return c.value
41
+}

+ 56
- 0
config/type_hostport.go Просмотреть файл

@@ -0,0 +1,56 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"strconv"
7
+)
8
+
9
+type TypeHostPort struct {
10
+	host TypeIP
11
+	port TypePort
12
+}
13
+
14
+func (c *TypeHostPort) UnmarshalText(data []byte) error {
15
+	if len(data) == 0 {
16
+		return nil
17
+	}
18
+
19
+	host, port, err := net.SplitHostPort(string(data))
20
+	if err != nil {
21
+		return fmt.Errorf("incorrect host:port syntax: %w", err)
22
+	}
23
+
24
+	if err := c.port.UnmarshalJSON([]byte(port)); err != nil {
25
+		return fmt.Errorf("incorrect port in host:port: %w", err)
26
+	}
27
+
28
+	if err := c.host.UnmarshalText([]byte(host)); err != nil {
29
+		return fmt.Errorf("incorrect host: %w", err)
30
+	}
31
+
32
+	return nil
33
+}
34
+
35
+func (c TypeHostPort) MarshalText() ([]byte, error) { // nolint: unparam
36
+	return []byte(c.String()), nil
37
+}
38
+
39
+func (c TypeHostPort) String() string {
40
+	return c.Value(net.IP{}, 0)
41
+}
42
+
43
+func (c TypeHostPort) HostValue(defaultValue net.IP) net.IP {
44
+    return c.host.Value(defaultValue)
45
+}
46
+
47
+func (c TypeHostPort) PortValue(defaultValue uint) uint {
48
+    return c.port.Value(defaultValue)
49
+}
50
+
51
+func (c TypeHostPort) Value(defaultHostValue net.IP, defaultPortValue uint) string {
52
+	host := c.HostValue(defaultHostValue)
53
+	port := c.PortValue(defaultPortValue)
54
+
55
+	return net.JoinHostPort(host.String(), strconv.Itoa(int(port)))
56
+}

+ 31
- 0
config/type_http_path.go Просмотреть файл

@@ -0,0 +1,31 @@
1
+package config
2
+
3
+import "strings"
4
+
5
+type TypeHTTPPath struct {
6
+	value string
7
+}
8
+
9
+func (c *TypeHTTPPath) UnmarshalText(data []byte) error { // nolint: unparam
10
+	if len(data) > 0 {
11
+		c.value = "/" + strings.Trim(string(data), "/")
12
+	}
13
+
14
+	return nil
15
+}
16
+
17
+func (c TypeHTTPPath) MarshalText() ([]byte, error) { // nolint: unparam
18
+	return []byte(c.String()), nil
19
+}
20
+
21
+func (c TypeHTTPPath) String() string {
22
+	return c.value
23
+}
24
+
25
+func (c TypeHTTPPath) Value(defaultValue string) string {
26
+	if c.value == "" {
27
+		return defaultValue
28
+	}
29
+
30
+	return c.value
31
+}

+ 45
- 0
config/type_ip.go Просмотреть файл

@@ -0,0 +1,45 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+)
7
+
8
+type TypeIP struct {
9
+	value net.IP
10
+}
11
+
12
+func (c *TypeIP) UnmarshalText(data []byte) error {
13
+	if len(data) == 0 {
14
+		return nil
15
+	}
16
+
17
+	ip := net.ParseIP(string(data))
18
+	if ip == nil {
19
+		return fmt.Errorf("incorrect ip address: %s", string(data))
20
+	}
21
+
22
+	c.value = ip
23
+
24
+	return nil
25
+}
26
+
27
+func (c *TypeIP) MarshalText() ([]byte, error) { // nolint: unparam
28
+	return []byte(c.String()), nil
29
+}
30
+
31
+func (c TypeIP) String() string {
32
+	if c.value == nil {
33
+		return ""
34
+	}
35
+
36
+	return c.value.String()
37
+}
38
+
39
+func (c TypeIP) Value(defaultValue net.IP) net.IP {
40
+	if c.value == nil {
41
+		return defaultValue
42
+	}
43
+
44
+	return c.value
45
+}

+ 42
- 0
config/type_metric_prefix.go Просмотреть файл

@@ -0,0 +1,42 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"regexp"
6
+)
7
+
8
+type TypeMetricPrefix struct {
9
+	value string
10
+}
11
+
12
+func (c *TypeMetricPrefix) UnmarshalText(data []byte) error {
13
+	if len(data) == 0 {
14
+		return nil
15
+	}
16
+
17
+	prefix := string(data)
18
+
19
+	if ok, err := regexp.MatchString("^[a-z0-9]+$", prefix); !ok || err != nil {
20
+		return fmt.Errorf("incorrect metric prefix: %s", prefix)
21
+	}
22
+
23
+	c.value = prefix
24
+
25
+	return nil
26
+}
27
+
28
+func (c TypeMetricPrefix) MarshalText() ([]byte, error) { // nolint: unparam
29
+	return []byte(c.String()), nil
30
+}
31
+
32
+func (c TypeMetricPrefix) String() string {
33
+	return c.value
34
+}
35
+
36
+func (c TypeMetricPrefix) Value(defaultValue string) string {
37
+	if c.value == "" {
38
+		return defaultValue
39
+	}
40
+
41
+	return c.value
42
+}

+ 45
- 0
config/type_port.go Просмотреть файл

@@ -0,0 +1,45 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strconv"
6
+)
7
+
8
+type TypePort struct {
9
+	value uint
10
+}
11
+
12
+func (c *TypePort) UnmarshalJSON(data []byte) error {
13
+	if len(data) == 0 {
14
+		return nil
15
+	}
16
+
17
+	intValue, err := strconv.ParseUint(string(data), 10, 16)
18
+	if err != nil {
19
+		return fmt.Errorf("port number is not a number: %w", err)
20
+	}
21
+
22
+	if intValue == 0 || intValue > 65536 {
23
+		return fmt.Errorf("port number should be 0 < portNo < 65536: %d", intValue)
24
+	}
25
+
26
+	c.value = uint(intValue)
27
+
28
+	return nil
29
+}
30
+
31
+func (c *TypePort) MarshalJSON() ([]byte, error) { // nolint: unparam
32
+    return []byte(c.String()), nil
33
+}
34
+
35
+func (c TypePort) String() string {
36
+	return strconv.Itoa(int(c.value))
37
+}
38
+
39
+func (c TypePort) Value(defaultValue uint) uint {
40
+	if c.value == 0 {
41
+		return defaultValue
42
+	}
43
+
44
+	return c.value
45
+}

+ 43
- 0
config/type_prefer_ip.go Просмотреть файл

@@ -0,0 +1,43 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+)
7
+
8
+type TypePreferIP struct {
9
+	value string
10
+}
11
+
12
+func (c *TypePreferIP) UnmarshalText(data []byte) error {
13
+	if len(data) == 0 {
14
+		return nil
15
+	}
16
+
17
+	text := strings.ToLower(string(data))
18
+
19
+	switch text {
20
+	case "prefer-ipv4", "prefer-ipv6", "only-ipv4", "only-ipv6":
21
+		c.value = text
22
+	default:
23
+		return fmt.Errorf("incorrect prefer-ip value: %s", string(data))
24
+	}
25
+
26
+	return nil
27
+}
28
+
29
+func (c TypePreferIP) MarshalText() ([]byte, error) { // nolint: unparam
30
+	return []byte(c.value), nil
31
+}
32
+
33
+func (c *TypePreferIP) String() string {
34
+	return c.value
35
+}
36
+
37
+func (c *TypePreferIP) Value(defaultValue string) string {
38
+	if c.value == "" {
39
+		return defaultValue
40
+	}
41
+
42
+	return c.value
43
+}

+ 45
- 0
config/type_url.go Просмотреть файл

@@ -0,0 +1,45 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"net/url"
6
+)
7
+
8
+type TypeURL struct {
9
+	value *url.URL
10
+}
11
+
12
+func (c *TypeURL) UnmarshalText(data []byte) error {
13
+	if len(data) == 0 {
14
+		return nil
15
+	}
16
+
17
+	value, err := url.Parse(string(data))
18
+	if err != nil {
19
+		return fmt.Errorf("incorrect URL: %w", err)
20
+	}
21
+
22
+	c.value = value
23
+
24
+	return nil
25
+}
26
+
27
+func (c *TypeURL) MarshalText() ([]byte, error) { // nolint: unparam
28
+	return []byte(c.String()), nil
29
+}
30
+
31
+func (c TypeURL) String() string {
32
+	if c.value == nil {
33
+		return ""
34
+	}
35
+
36
+	return c.value.String()
37
+}
38
+
39
+func (c TypeURL) Value(defaultValue *url.URL) *url.URL {
40
+	if c.value == nil {
41
+		return defaultValue
42
+	}
43
+
44
+	return c.value
45
+}

+ 2
- 1
utils.go Просмотреть файл

@@ -8,10 +8,11 @@ import (
8 8
 	"net/http"
9 9
 	"net/url"
10 10
 
11
+	"github.com/9seconds/mtg/v2/config"
11 12
 	"github.com/9seconds/mtg/v2/mtglib/network"
12 13
 )
13 14
 
14
-func makeNetwork(conf *config) (network.Network, error) {
15
+func makeNetwork(conf *config.Config) (network.Network, error) {
15 16
 	tcpTimeout := conf.Network.Timeout.TCP.Value(network.DefaultTimeout)
16 17
 	idleTimeout := conf.Network.Timeout.Idle.Value(network.DefaultIdleTimeout)
17 18
 	dohIP := conf.Network.DOHIP.Value(net.ParseIP(network.DefaultDOHHostname)).String()

Загрузка…
Отмена
Сохранить