Browse Source

Add public-ipv4/public-ipv6 config options for manual IP override

On some servers ifconfig.co is unreachable (e.g. Hetzner, AdGuard DNS
blocklists), causing 'mtg doctor' SNI-DNS check and 'mtg access' link
generation to fail. New config options allow specifying public IPs
manually, with automatic detection as fallback.

Fixes #405
tags/v2.2.5^2^2
Alexey Dolotov 1 month ago
parent
commit
2b07c0037e

+ 7
- 0
example.config.toml View File

48
 #     Only ipv4 connectivity is used
48
 #     Only ipv4 connectivity is used
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
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.
55
+# public-ipv4 = "1.2.3.4"
56
+# public-ipv6 = "2001:db8::1"
57
+
51
 # If this setting is set, then mtg will try to get proxy updates from Telegram
58
 # If this setting is set, then mtg will try to get proxy updates from Telegram
52
 # Usually this is completely fine to have it disabled, because mtg has a list
59
 # Usually this is completely fine to have it disabled, because mtg has a list
53
 # of some core proxies hardcoded.
60
 # of some core proxies hardcoded.

+ 6
- 0
internal/cli/access.go View File

58
 
58
 
59
 	wg.Go(func() {
59
 	wg.Go(func() {
60
 		ip := a.PublicIPv4
60
 		ip := a.PublicIPv4
61
+		if ip == nil {
62
+			ip = conf.PublicIPv4.Get(nil)
63
+		}
61
 		if ip == nil {
64
 		if ip == nil {
62
 			ip = getIP(ntw, "tcp4")
65
 			ip = getIP(ntw, "tcp4")
63
 		}
66
 		}
70
 	})
73
 	})
71
 	wg.Go(func() {
74
 	wg.Go(func() {
72
 		ip := a.PublicIPv6
75
 		ip := a.PublicIPv6
76
+		if ip == nil {
77
+			ip = conf.PublicIPv6.Get(nil)
78
+		}
73
 		if ip == nil {
79
 		if ip == nil {
74
 			ip = getIP(ntw, "tcp6")
80
 			ip = getIP(ntw, "tcp6")
75
 		}
81
 		}

+ 10
- 3
internal/cli/doctor.go View File

332
 		return false
332
 		return false
333
 	}
333
 	}
334
 
334
 
335
-	ourIP4 := getIP(ntw, "tcp4")
336
-	ourIP6 := getIP(ntw, "tcp6")
335
+	ourIP4 := d.conf.PublicIPv4.Get(nil)
336
+	if ourIP4 == nil {
337
+		ourIP4 = getIP(ntw, "tcp4")
338
+	}
339
+
340
+	ourIP6 := d.conf.PublicIPv6.Get(nil)
341
+	if ourIP6 == nil {
342
+		ourIP6 = getIP(ntw, "tcp6")
343
+	}
337
 
344
 
338
 	if ourIP4 == nil && ourIP6 == nil {
345
 	if ourIP4 == nil && ourIP6 == nil {
339
 		tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
346
 		tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
340
 			"description": "cannot detect public IP address",
347
 			"description": "cannot detect public IP address",
341
-			"error":       errors.New("ifconfig.co is unreachable for both IPv4 and IPv6"),
348
+			"error":       errors.New("cannot detect automatically and public-ipv4/public-ipv6 are not set in config"),
342
 		})
349
 		})
343
 		return false
350
 		return false
344
 	}
351
 	}

+ 2
- 0
internal/config/config.go View File

35
 	DomainFrontingProxyProtocol TypeBool        `json:"domainFrontingProxyProtocol"`
35
 	DomainFrontingProxyProtocol TypeBool        `json:"domainFrontingProxyProtocol"`
36
 	TolerateTimeSkewness        TypeDuration    `json:"tolerateTimeSkewness"`
36
 	TolerateTimeSkewness        TypeDuration    `json:"tolerateTimeSkewness"`
37
 	Concurrency                 TypeConcurrency `json:"concurrency"`
37
 	Concurrency                 TypeConcurrency `json:"concurrency"`
38
+	PublicIPv4                  TypeIP          `json:"publicIpv4"`
39
+	PublicIPv6                  TypeIP          `json:"publicIpv6"`
38
 	DomainFronting              struct {
40
 	DomainFronting              struct {
39
 		IP            TypeIP   `json:"ip"`
41
 		IP            TypeIP   `json:"ip"`
40
 		Port          TypePort `json:"port"`
42
 		Port          TypePort `json:"port"`

+ 26
- 0
internal/config/config_test.go View File

42
 	suite.Equal("0.0.0.0:3128", conf.BindTo.String())
42
 	suite.Equal("0.0.0.0:3128", conf.BindTo.String())
43
 }
43
 }
44
 
44
 
45
+func (suite *ConfigTestSuite) TestParsePublicIP() {
46
+	conf, err := config.Parse(suite.ReadConfig("public_ip.toml"))
47
+	suite.NoError(err)
48
+	suite.Equal("203.0.113.1", conf.PublicIPv4.Get(nil).String())
49
+	suite.Equal("2001:db8::1", conf.PublicIPv6.Get(nil).String())
50
+}
51
+
52
+func (suite *ConfigTestSuite) TestParsePublicIPv4Only() {
53
+	conf, err := config.Parse(suite.ReadConfig("public_ip_v4_only.toml"))
54
+	suite.NoError(err)
55
+	suite.Equal("203.0.113.1", conf.PublicIPv4.Get(nil).String())
56
+	suite.Nil(conf.PublicIPv6.Get(nil))
57
+}
58
+
59
+func (suite *ConfigTestSuite) TestParsePublicIPInvalid() {
60
+	_, err := config.Parse(suite.ReadConfig("public_ip_invalid.toml"))
61
+	suite.Error(err)
62
+}
63
+
64
+func (suite *ConfigTestSuite) TestParsePublicIPNotSet() {
65
+	conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
66
+	suite.NoError(err)
67
+	suite.Nil(conf.PublicIPv4.Get(nil))
68
+	suite.Nil(conf.PublicIPv6.Get(nil))
69
+}
70
+
45
 func (suite *ConfigTestSuite) TestString() {
71
 func (suite *ConfigTestSuite) TestString() {
46
 	conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
72
 	conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
47
 	suite.NoError(err)
73
 	suite.NoError(err)

+ 2
- 0
internal/config/parse.go View File

21
 	DomainFrontingProxyProtocol bool   `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"`
21
 	DomainFrontingProxyProtocol bool   `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"`
22
 	TolerateTimeSkewness        string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
22
 	TolerateTimeSkewness        string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
23
 	Concurrency                 uint   `toml:"concurrency" json:"concurrency,omitempty"`
23
 	Concurrency                 uint   `toml:"concurrency" json:"concurrency,omitempty"`
24
+	PublicIPv4                  string `toml:"public-ipv4" json:"publicIpv4,omitempty"`
25
+	PublicIPv6                  string `toml:"public-ipv6" json:"publicIpv6,omitempty"`
24
 	DomainFronting              struct {
26
 	DomainFronting              struct {
25
 		IP            string `toml:"ip" json:"ip,omitempty"`
27
 		IP            string `toml:"ip" json:"ip,omitempty"`
26
 		Port          uint   `toml:"port" json:"port,omitempty"`
28
 		Port          uint   `toml:"port" json:"port,omitempty"`

+ 4
- 0
internal/config/testdata/public_ip.toml View File

1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
2
+bind-to = "0.0.0.0:3128"
3
+public-ipv4 = "203.0.113.1"
4
+public-ipv6 = "2001:db8::1"

+ 3
- 0
internal/config/testdata/public_ip_invalid.toml View File

1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
2
+bind-to = "0.0.0.0:3128"
3
+public-ipv4 = "not-an-ip"

+ 3
- 0
internal/config/testdata/public_ip_v4_only.toml View File

1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
2
+bind-to = "0.0.0.0:3128"
3
+public-ipv4 = "203.0.113.1"

Loading…
Cancel
Save