Explorar el Código

Merge 8e4e0f31a1 into 827bbd6900

pull/474/merge
Alexey Dolotov hace 4 días
padre
commit
b6298c5e71
No account linked to committer's email address

+ 15
- 3
example.config.toml Ver fichero

49
 prefer-ip = "prefer-ipv6"
49
 prefer-ip = "prefer-ipv6"
50
 
50
 
51
 # Public IP addresses of this server. Used by 'mtg access' to generate
51
 # Public IP addresses of this server. Used by 'mtg access' to generate
52
-# proxy links and by 'mtg doctor' to validate SNI-DNS match.
53
-# If not set, mtg tries to detect them automatically via ifconfig.co.
54
-# Set these if ifconfig.co is unreachable from your server.
52
+# proxy links and by 'mtg doctor' / proxy startup to validate SNI-DNS match.
53
+# If not set, mtg tries to detect them automatically by querying the public
54
+# HTTPS endpoints listed in network.public-ip-endpoints (see below).
55
+# Set these explicitly if those endpoints are unreachable from your server.
55
 # public-ipv4 = "1.2.3.4"
56
 # public-ipv4 = "1.2.3.4"
56
 # public-ipv6 = "2001:db8::1"
57
 # public-ipv6 = "2001:db8::1"
57
 
58
 
200
     # "socks5://user:password@host:port"
201
     # "socks5://user:password@host:port"
201
 ]
202
 ]
202
 
203
 
204
+# HTTPS endpoints used to discover this server's public IPv4/IPv6 when
205
+# public-ipv4 / public-ipv6 are not set. Each must return the client's public
206
+# IP as a single address in the plain-text response body. mtg tries them in
207
+# order and uses the first that succeeds. The default is shown below; setting
208
+# this option overrides the default entirely.
209
+# public-ip-endpoints = [
210
+#     "https://ifconfig.co",
211
+#     "https://icanhazip.com",
212
+#     "https://ifconfig.me",
213
+# ]
214
+
203
 # network timeouts define different settings for timeouts. tcp timeout
215
 # network timeouts define different settings for timeouts. tcp timeout
204
 # define a global timeout on establishing of network connections. idle
216
 # define a global timeout on establishing of network connections. idle
205
 # means a timeout on pumping data between sockset when nothing is
217
 # means a timeout on pumping data between sockset when nothing is

+ 8
- 2
internal/cli/access.go Ver fichero

1
 package cli
1
 package cli
2
 
2
 
3
 import (
3
 import (
4
+	"context"
4
 	"encoding/json"
5
 	"encoding/json"
5
 	"fmt"
6
 	"fmt"
6
 	"net"
7
 	"net"
8
 	"os"
9
 	"os"
9
 	"strconv"
10
 	"strconv"
10
 	"sync"
11
 	"sync"
12
+	"time"
11
 
13
 
12
 	"github.com/9seconds/mtg/v2/internal/config"
14
 	"github.com/9seconds/mtg/v2/internal/config"
13
 	"github.com/9seconds/mtg/v2/internal/utils"
15
 	"github.com/9seconds/mtg/v2/internal/utils"
54
 		return fmt.Errorf("cannot init network: %w", err)
56
 		return fmt.Errorf("cannot init network: %w", err)
55
 	}
57
 	}
56
 
58
 
59
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
60
+	defer cancel()
61
+
62
+	endpoints := resolvePublicIPEndpoints(conf.Network.PublicIPEndpoints)
57
 	wg := &sync.WaitGroup{}
63
 	wg := &sync.WaitGroup{}
58
 
64
 
59
 	wg.Go(func() {
65
 	wg.Go(func() {
62
 			ip = conf.PublicIPv4.Get(nil)
68
 			ip = conf.PublicIPv4.Get(nil)
63
 		}
69
 		}
64
 		if ip == nil {
70
 		if ip == nil {
65
-			ip = getIP(ntw, "tcp4")
71
+			ip = getIP(ctx, ntw, "tcp4", endpoints)
66
 		}
72
 		}
67
 
73
 
68
 		if ip != nil {
74
 		if ip != nil {
77
 			ip = conf.PublicIPv6.Get(nil)
83
 			ip = conf.PublicIPv6.Get(nil)
78
 		}
84
 		}
79
 		if ip == nil {
85
 		if ip == nil {
80
-			ip = getIP(ntw, "tcp6")
86
+			ip = getIP(ctx, ntw, "tcp6", endpoints)
81
 		}
87
 		}
82
 
88
 
83
 		if ip != nil {
89
 		if ip != nil {

+ 58
- 31
internal/cli/doctor.go Ver fichero

53
 	)
53
 	)
54
 
54
 
55
 	tplODNSSNIMatch = template.Must(
55
 	tplODNSSNIMatch = template.Must(
56
-		template.New("").Parse("  ✅ IP address {{ .ip }} matches secret hostname {{ .hostname }}\n"),
56
+		template.New("").Parse("  ✅ Secret hostname {{ .hostname }} matches our public IP ({{ .our }}); resolved: {{ .resolved }}\n"),
57
 	)
57
 	)
58
 	tplEDNSSNIMatch = template.Must(
58
 	tplEDNSSNIMatch = template.Must(
59
-		template.New("").Parse("  ❌ Hostname {{ .hostname }} {{ if .resolved }}is resolved to {{ .resolved }} addresses, not {{ if .ip4 }}{{ .ip4 }}{{ else }}{{ .ip6 }}{{ end }}{{ else }}cannot be resolved to any host{{ end }}\n"),
59
+		template.New("").Parse("  ❌ Secret hostname {{ .hostname }} resolves to {{ .resolved }} but our public IP is {{ .our }}{{ if .families }} (mismatched families: {{ .families }}){{ end }}\n"),
60
+	)
61
+	tplEDNSSNINoResolve = template.Must(
62
+		template.New("").Parse("  ❌ Secret hostname {{ .hostname }} cannot be resolved to any address\n"),
60
 	)
63
 	)
61
 
64
 
62
 	tplOFrontingDomain = template.Must(
65
 	tplOFrontingDomain = template.Must(
349
 }
352
 }
350
 
353
 
351
 func (d *Doctor) checkSecretHost(resolver *net.Resolver, ntw mtglib.Network) bool {
354
 func (d *Doctor) checkSecretHost(resolver *net.Resolver, ntw mtglib.Network) bool {
352
-	addresses, err := resolver.LookupIPAddr(context.Background(), d.conf.Secret.Host)
353
-	if err != nil {
355
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
356
+	defer cancel()
357
+
358
+	res := runSNICheck(ctx, resolver, d.conf, ntw)
359
+
360
+	if res.ResolveErr != nil {
354
 		tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
361
 		tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
355
-			"description": fmt.Sprintf("cannot resolve DNS name of %s", d.conf.Secret.Host),
356
-			"error":       err,
362
+			"description": fmt.Sprintf("cannot resolve DNS name of %s", res.Host),
363
+			"error":       res.ResolveErr,
357
 		})
364
 		})
358
 		return false
365
 		return false
359
 	}
366
 	}
360
 
367
 
361
-	ourIP4 := d.conf.PublicIPv4.Get(nil)
362
-	if ourIP4 == nil {
363
-		ourIP4 = getIP(ntw, "tcp4")
364
-	}
365
-
366
-	ourIP6 := d.conf.PublicIPv6.Get(nil)
367
-	if ourIP6 == nil {
368
-		ourIP6 = getIP(ntw, "tcp6")
369
-	}
370
-
371
-	if ourIP4 == nil && ourIP6 == nil {
368
+	if !res.Known() {
372
 		tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
369
 		tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
373
 			"description": "cannot detect public IP address",
370
 			"description": "cannot detect public IP address",
374
 			"error":       errors.New("cannot detect automatically and public-ipv4/public-ipv6 are not set in config"),
371
 			"error":       errors.New("cannot detect automatically and public-ipv4/public-ipv6 are not set in config"),
376
 		return false
373
 		return false
377
 	}
374
 	}
378
 
375
 
379
-	strAddresses := []string{}
380
-	for _, value := range addresses {
381
-		if (ourIP4 != nil && value.IP.String() == ourIP4.String()) ||
382
-			(ourIP6 != nil && value.IP.String() == ourIP6.String()) {
383
-			tplODNSSNIMatch.Execute(os.Stdout, map[string]any{ //nolint: errcheck
384
-				"ip":       value.IP,
385
-				"hostname": d.conf.Secret.Host,
386
-			})
387
-			return true
376
+	if len(res.Resolved) == 0 {
377
+		tplEDNSSNINoResolve.Execute(os.Stdout, map[string]any{ //nolint: errcheck
378
+			"hostname": res.Host,
379
+		})
380
+		return false
381
+	}
382
+
383
+	resolved := make([]string, 0, len(res.Resolved))
384
+	for _, ip := range res.Resolved {
385
+		resolved = append(resolved, `"`+ip.String()+`"`)
386
+	}
387
+
388
+	our := ""
389
+	if res.OurIPv4 != nil {
390
+		our = res.OurIPv4.String()
391
+	}
392
+
393
+	if res.OurIPv6 != nil {
394
+		if our != "" {
395
+			our += "/"
388
 		}
396
 		}
389
 
397
 
390
-		strAddresses = append(strAddresses, `"`+value.IP.String()+`"`)
398
+		our += res.OurIPv6.String()
399
+	}
400
+
401
+	if res.OK() {
402
+		tplODNSSNIMatch.Execute(os.Stdout, map[string]any{ //nolint: errcheck
403
+			"hostname": res.Host,
404
+			"resolved": strings.Join(resolved, ", "),
405
+			"our":      our,
406
+		})
407
+		return true
408
+	}
409
+
410
+	mismatched := []string{}
411
+
412
+	if res.OurIPv4 != nil && !res.IPv4Match {
413
+		mismatched = append(mismatched, "IPv4")
414
+	}
415
+
416
+	if res.OurIPv6 != nil && !res.IPv6Match {
417
+		mismatched = append(mismatched, "IPv6")
391
 	}
418
 	}
392
 
419
 
393
 	tplEDNSSNIMatch.Execute(os.Stdout, map[string]any{ //nolint: errcheck
420
 	tplEDNSSNIMatch.Execute(os.Stdout, map[string]any{ //nolint: errcheck
394
-		"hostname": d.conf.Secret.Host,
395
-		"resolved": strings.Join(strAddresses, ", "),
396
-		"ip4":      ourIP4,
397
-		"ip6":      ourIP6,
421
+		"hostname": res.Host,
422
+		"resolved": strings.Join(resolved, ", "),
423
+		"our":      our,
424
+		"families": strings.Join(mismatched, ", "),
398
 	})
425
 	})
399
 
426
 
400
 	return false
427
 	return false

+ 27
- 40
internal/cli/run_proxy.go Ver fichero

6
 	"net"
6
 	"net"
7
 	"os"
7
 	"os"
8
 	"strings"
8
 	"strings"
9
+	"time"
9
 
10
 
10
 	"github.com/9seconds/mtg/v2/antireplay"
11
 	"github.com/9seconds/mtg/v2/antireplay"
11
 	"github.com/9seconds/mtg/v2/events"
12
 	"github.com/9seconds/mtg/v2/events"
209
 }
210
 }
210
 
211
 
211
 func warnSNIMismatch(conf *config.Config, ntw mtglib.Network, log mtglib.Logger) {
212
 func warnSNIMismatch(conf *config.Config, ntw mtglib.Network, log mtglib.Logger) {
212
-	host := conf.Secret.Host
213
-	if host == "" {
213
+	if conf.Secret.Host == "" {
214
 		return
214
 		return
215
 	}
215
 	}
216
 
216
 
217
-	addresses, err := net.DefaultResolver.LookupIPAddr(context.Background(), host)
218
-	if err != nil {
219
-		log.BindStr("hostname", host).
220
-			WarningError("SNI-DNS check: cannot resolve secret hostname", err)
221
-		return
222
-	}
217
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
218
+	defer cancel()
223
 
219
 
224
-	ourIP4 := conf.PublicIPv4.Get(nil)
225
-	if ourIP4 == nil {
226
-		ourIP4 = getIP(ntw, "tcp4")
227
-	}
220
+	res := runSNICheck(ctx, net.DefaultResolver, conf, ntw)
228
 
221
 
229
-	ourIP6 := conf.PublicIPv6.Get(nil)
230
-	if ourIP6 == nil {
231
-		ourIP6 = getIP(ntw, "tcp6")
222
+	if res.ResolveErr != nil {
223
+		log.BindStr("hostname", res.Host).
224
+			WarningError("SNI-DNS check: cannot resolve secret hostname", res.ResolveErr)
225
+		return
232
 	}
226
 	}
233
 
227
 
234
-	if ourIP4 == nil && ourIP6 == nil {
228
+	if !res.Known() {
235
 		log.Warning("SNI-DNS check: cannot detect public IP address; set public-ipv4/public-ipv6 in config or run 'mtg doctor'")
229
 		log.Warning("SNI-DNS check: cannot detect public IP address; set public-ipv4/public-ipv6 in config or run 'mtg doctor'")
236
 		return
230
 		return
237
 	}
231
 	}
238
 
232
 
239
-	v4Match := ourIP4 == nil
240
-	v6Match := ourIP6 == nil
241
-
242
-	for _, addr := range addresses {
243
-		if ourIP4 != nil && addr.IP.String() == ourIP4.String() {
244
-			v4Match = true
245
-		}
246
-
247
-		if ourIP6 != nil && addr.IP.String() == ourIP6.String() {
248
-			v6Match = true
249
-		}
233
+	if len(res.Resolved) == 0 {
234
+		log.BindStr("hostname", res.Host).
235
+			Warning("SNI-DNS check: secret hostname does not resolve to any address")
236
+		return
250
 	}
237
 	}
251
 
238
 
252
-	if v4Match && v6Match {
239
+	if res.OK() {
253
 		return
240
 		return
254
 	}
241
 	}
255
 
242
 
256
-	resolved := make([]string, 0, len(addresses))
257
-	for _, addr := range addresses {
258
-		resolved = append(resolved, addr.IP.String())
243
+	resolved := make([]string, 0, len(res.Resolved))
244
+	for _, ip := range res.Resolved {
245
+		resolved = append(resolved, ip.String())
259
 	}
246
 	}
260
 
247
 
261
 	our := ""
248
 	our := ""
262
-	if ourIP4 != nil {
263
-		our = ourIP4.String()
249
+	if res.OurIPv4 != nil {
250
+		our = res.OurIPv4.String()
264
 	}
251
 	}
265
 
252
 
266
-	if ourIP6 != nil {
253
+	if res.OurIPv6 != nil {
267
 		if our != "" {
254
 		if our != "" {
268
 			our += "/"
255
 			our += "/"
269
 		}
256
 		}
270
 
257
 
271
-		our += ourIP6.String()
258
+		our += res.OurIPv6.String()
272
 	}
259
 	}
273
 
260
 
274
-	entry := log.BindStr("hostname", host).
261
+	entry := log.BindStr("hostname", res.Host).
275
 		BindStr("resolved", strings.Join(resolved, ", ")).
262
 		BindStr("resolved", strings.Join(resolved, ", ")).
276
 		BindStr("public_ip", our)
263
 		BindStr("public_ip", our)
277
 
264
 
278
-	if ourIP4 != nil {
279
-		entry = entry.BindStr("ipv4_match", fmt.Sprintf("%t", v4Match))
265
+	if res.OurIPv4 != nil {
266
+		entry = entry.BindStr("ipv4_match", fmt.Sprintf("%t", res.IPv4Match))
280
 	}
267
 	}
281
 
268
 
282
-	if ourIP6 != nil {
283
-		entry = entry.BindStr("ipv6_match", fmt.Sprintf("%t", v6Match))
269
+	if res.OurIPv6 != nil {
270
+		entry = entry.BindStr("ipv6_match", fmt.Sprintf("%t", res.IPv6Match))
284
 	}
271
 	}
285
 
272
 
286
 	entry.Warning("SNI-DNS mismatch: secret hostname does not resolve to this server's public IP. " +
273
 	entry.Warning("SNI-DNS mismatch: secret hostname does not resolve to this server's public IP. " +

+ 112
- 0
internal/cli/sni_check.go Ver fichero

1
+package cli
2
+
3
+import (
4
+	"context"
5
+	"net"
6
+	"sync"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/9seconds/mtg/v2/mtglib"
10
+)
11
+
12
+// sniCheckResult captures the outcome of comparing the secret hostname's DNS
13
+// records with this server's public IP addresses.
14
+//
15
+// IPv4Match/IPv6Match are true when either a matching record was found, or
16
+// when the corresponding public IP could not be detected — in which case
17
+// there is nothing to compare against.
18
+type sniCheckResult struct {
19
+	Host       string
20
+	Resolved   []net.IP
21
+	OurIPv4    net.IP
22
+	OurIPv6    net.IP
23
+	IPv4Match  bool
24
+	IPv6Match  bool
25
+	ResolveErr error
26
+}
27
+
28
+// Known reports whether at least one public IP family was detected.
29
+func (r sniCheckResult) Known() bool {
30
+	return r.OurIPv4 != nil || r.OurIPv6 != nil
31
+}
32
+
33
+// OK reports whether the check produced a clean result: the hostname was
34
+// resolved, at least one public IP family is known, and every known family
35
+// matches a resolved record.
36
+func (r sniCheckResult) OK() bool {
37
+	if r.Host == "" {
38
+		return true
39
+	}
40
+
41
+	if r.ResolveErr != nil || !r.Known() {
42
+		return false
43
+	}
44
+
45
+	return r.IPv4Match && r.IPv6Match
46
+}
47
+
48
+// runSNICheck resolves conf.Secret.Host and compares the result with the
49
+// server's public IPv4 and IPv6. Public IPs come from config first and fall
50
+// back to on-the-fly detection via ntw. IP detection for the two families
51
+// runs concurrently and honors ctx — callers should supply a deadline,
52
+// since the HTTP fallback can otherwise block startup indefinitely.
53
+func runSNICheck(ctx context.Context,
54
+	resolver *net.Resolver,
55
+	conf *config.Config,
56
+	ntw mtglib.Network,
57
+) sniCheckResult {
58
+	res := sniCheckResult{Host: conf.Secret.Host}
59
+
60
+	if res.Host == "" {
61
+		res.IPv4Match = true
62
+		res.IPv6Match = true
63
+
64
+		return res
65
+	}
66
+
67
+	addrs, err := resolver.LookupIPAddr(ctx, res.Host)
68
+	if err != nil {
69
+		res.ResolveErr = err
70
+
71
+		return res
72
+	}
73
+
74
+	res.Resolved = make([]net.IP, 0, len(addrs))
75
+	for _, a := range addrs {
76
+		res.Resolved = append(res.Resolved, a.IP)
77
+	}
78
+
79
+	endpoints := resolvePublicIPEndpoints(conf.Network.PublicIPEndpoints)
80
+	wg := sync.WaitGroup{}
81
+
82
+	wg.Go(func() {
83
+		res.OurIPv4 = conf.PublicIPv4.Get(nil)
84
+		if res.OurIPv4 == nil {
85
+			res.OurIPv4 = getIP(ctx, ntw, "tcp4", endpoints)
86
+		}
87
+	})
88
+
89
+	wg.Go(func() {
90
+		res.OurIPv6 = conf.PublicIPv6.Get(nil)
91
+		if res.OurIPv6 == nil {
92
+			res.OurIPv6 = getIP(ctx, ntw, "tcp6", endpoints)
93
+		}
94
+	})
95
+
96
+	wg.Wait()
97
+
98
+	res.IPv4Match = res.OurIPv4 == nil
99
+	res.IPv6Match = res.OurIPv6 == nil
100
+
101
+	for _, ip := range res.Resolved {
102
+		if res.OurIPv4 != nil && ip.String() == res.OurIPv4.String() {
103
+			res.IPv4Match = true
104
+		}
105
+
106
+		if res.OurIPv6 != nil && ip.String() == res.OurIPv6.String() {
107
+			res.IPv6Match = true
108
+		}
109
+	}
110
+
111
+	return res
112
+}

+ 49
- 7
internal/cli/utils.go Ver fichero

8
 	"strings"
8
 	"strings"
9
 
9
 
10
 	"github.com/9seconds/mtg/v2/essentials"
10
 	"github.com/9seconds/mtg/v2/essentials"
11
+	"github.com/9seconds/mtg/v2/internal/config"
11
 	"github.com/9seconds/mtg/v2/mtglib"
12
 	"github.com/9seconds/mtg/v2/mtglib"
12
 )
13
 )
13
 
14
 
14
-func getIP(ntw mtglib.Network, protocol string) net.IP {
15
+// defaultPublicIPEndpoints is the fallback used when network.public-ip-endpoints
16
+// is not set in config. Each endpoint must return the client's public IP as a
17
+// single address in the plain-text response body.
18
+var defaultPublicIPEndpoints = []string{
19
+	"https://ifconfig.co",
20
+	"https://icanhazip.com",
21
+	"https://ifconfig.me",
22
+}
23
+
24
+// resolvePublicIPEndpoints returns the configured endpoint list, falling back
25
+// to defaultPublicIPEndpoints when none are configured.
26
+func resolvePublicIPEndpoints(configured []config.TypeHttpsURL) []string {
27
+	if len(configured) == 0 {
28
+		return defaultPublicIPEndpoints
29
+	}
30
+
31
+	out := make([]string, 0, len(configured))
32
+	for _, u := range configured {
33
+		if v := u.Get(nil); v != nil {
34
+			out = append(out, v.String())
35
+		}
36
+	}
37
+
38
+	if len(out) == 0 {
39
+		return defaultPublicIPEndpoints
40
+	}
41
+
42
+	return out
43
+}
44
+
45
+func getIP(ctx context.Context, ntw mtglib.Network, protocol string, endpoints []string) net.IP {
15
 	dialer := ntw.NativeDialer()
46
 	dialer := ntw.NativeDialer()
16
 	client := ntw.MakeHTTPClient(func(ctx context.Context, network, address string) (essentials.Conn, error) {
47
 	client := ntw.MakeHTTPClient(func(ctx context.Context, network, address string) (essentials.Conn, error) {
17
 		conn, err := dialer.DialContext(ctx, protocol, address)
48
 		conn, err := dialer.DialContext(ctx, protocol, address)
21
 		return essentials.WrapNetConn(conn), err
52
 		return essentials.WrapNetConn(conn), err
22
 	})
53
 	})
23
 
54
 
24
-	req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) //nolint: noctx
25
-	if err != nil {
26
-		panic(err)
55
+	for _, endpoint := range endpoints {
56
+		if ip := fetchPublicIP(ctx, client, endpoint); ip != nil {
57
+			return ip
58
+		}
27
 	}
59
 	}
28
 
60
 
29
-	req.Header.Add("Accept", "text/plain")
61
+	return nil
62
+}
30
 
63
 
31
-	resp, err := client.Do(req)
64
+func fetchPublicIP(ctx context.Context, client *http.Client, endpoint string) net.IP {
65
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
32
 	if err != nil {
66
 	if err != nil {
33
 		return nil
67
 		return nil
34
 	}
68
 	}
35
 
69
 
36
-	if resp.StatusCode != http.StatusOK {
70
+	req.Header.Set("Accept", "text/plain")
71
+	req.Header.Set("User-Agent", "curl/8")
72
+
73
+	resp, err := client.Do(req)
74
+	if err != nil {
37
 		return nil
75
 		return nil
38
 	}
76
 	}
39
 
77
 
42
 		resp.Body.Close()              //nolint: errcheck
80
 		resp.Body.Close()              //nolint: errcheck
43
 	}()
81
 	}()
44
 
82
 
83
+	if resp.StatusCode != http.StatusOK {
84
+		return nil
85
+	}
86
+
45
 	data, err := io.ReadAll(resp.Body)
87
 	data, err := io.ReadAll(resp.Body)
46
 	if err != nil {
88
 	if err != nil {
47
 		return nil
89
 		return nil

+ 4
- 3
internal/config/config.go Ver fichero

71
 			Interval TypeDuration    `json:"interval"`
71
 			Interval TypeDuration    `json:"interval"`
72
 			Count    TypeConcurrency `json:"count"`
72
 			Count    TypeConcurrency `json:"count"`
73
 		} `json:"keepAlive"`
73
 		} `json:"keepAlive"`
74
-		DOHIP   TypeIP         `json:"dohIp"`
75
-		DNS     TypeDNSURI     `json:"dns"`
76
-		Proxies []TypeProxyURL `json:"proxies"`
74
+		DOHIP             TypeIP         `json:"dohIp"`
75
+		DNS               TypeDNSURI     `json:"dns"`
76
+		Proxies           []TypeProxyURL `json:"proxies"`
77
+		PublicIPEndpoints []TypeHttpsURL `json:"publicIpEndpoints"`
77
 	} `json:"network"`
78
 	} `json:"network"`
78
 	Stats struct {
79
 	Stats struct {
79
 		StatsD struct {
80
 		StatsD struct {

+ 4
- 3
internal/config/parse.go Ver fichero

66
 			Interval string `toml:"interval" json:"interval,omitempty"`
66
 			Interval string `toml:"interval" json:"interval,omitempty"`
67
 			Count    uint   `toml:"count" json:"count,omitempty"`
67
 			Count    uint   `toml:"count" json:"count,omitempty"`
68
 		} `toml:"keep-alive" json:"keepAlive,omitempty"`
68
 		} `toml:"keep-alive" json:"keepAlive,omitempty"`
69
-		DOHIP   string   `toml:"doh-ip" json:"dohIp,omitempty"`
70
-		DNS     string   `toml:"dns" json:"dns,omitempty"`
71
-		Proxies []string `toml:"proxies" json:"proxies,omitempty"`
69
+		DOHIP             string   `toml:"doh-ip" json:"dohIp,omitempty"`
70
+		DNS               string   `toml:"dns" json:"dns,omitempty"`
71
+		Proxies           []string `toml:"proxies" json:"proxies,omitempty"`
72
+		PublicIPEndpoints []string `toml:"public-ip-endpoints" json:"publicIpEndpoints,omitempty"`
72
 	} `toml:"network" json:"network,omitempty"`
73
 	} `toml:"network" json:"network,omitempty"`
73
 	Stats struct {
74
 	Stats struct {
74
 		StatsD struct {
75
 		StatsD struct {

Loading…
Cancelar
Guardar