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
This commit is contained in:
Alexey Dolotov
2026-03-29 00:47:49 +03:00
parent cc4b6ce2f4
commit 2b07c0037e
9 changed files with 63 additions and 3 deletions
+7
View File
@@ -48,6 +48,13 @@ concurrency = 8192
# Only ipv4 connectivity is used # Only ipv4 connectivity is used
prefer-ip = "prefer-ipv6" prefer-ip = "prefer-ipv6"
# Public IP addresses of this server. Used by 'mtg access' to generate
# proxy links and by 'mtg doctor' to validate SNI-DNS match.
# If not set, mtg tries to detect them automatically via ifconfig.co.
# Set these if ifconfig.co is unreachable from your server.
# public-ipv4 = "1.2.3.4"
# public-ipv6 = "2001:db8::1"
# If this setting is set, then mtg will try to get proxy updates from Telegram # If this setting is set, then mtg will try to get proxy updates from Telegram
# Usually this is completely fine to have it disabled, because mtg has a list # Usually this is completely fine to have it disabled, because mtg has a list
# of some core proxies hardcoded. # of some core proxies hardcoded.
+6
View File
@@ -58,6 +58,9 @@ func (a *Access) Run(cli *CLI, version string) error {
wg.Go(func() { wg.Go(func() {
ip := a.PublicIPv4 ip := a.PublicIPv4
if ip == nil {
ip = conf.PublicIPv4.Get(nil)
}
if ip == nil { if ip == nil {
ip = getIP(ntw, "tcp4") ip = getIP(ntw, "tcp4")
} }
@@ -70,6 +73,9 @@ func (a *Access) Run(cli *CLI, version string) error {
}) })
wg.Go(func() { wg.Go(func() {
ip := a.PublicIPv6 ip := a.PublicIPv6
if ip == nil {
ip = conf.PublicIPv6.Get(nil)
}
if ip == nil { if ip == nil {
ip = getIP(ntw, "tcp6") ip = getIP(ntw, "tcp6")
} }
+10 -3
View File
@@ -332,13 +332,20 @@ func (d *Doctor) checkSecretHost(resolver *net.Resolver, ntw mtglib.Network) boo
return false return false
} }
ourIP4 := getIP(ntw, "tcp4") ourIP4 := d.conf.PublicIPv4.Get(nil)
ourIP6 := getIP(ntw, "tcp6") if ourIP4 == nil {
ourIP4 = getIP(ntw, "tcp4")
}
ourIP6 := d.conf.PublicIPv6.Get(nil)
if ourIP6 == nil {
ourIP6 = getIP(ntw, "tcp6")
}
if ourIP4 == nil && ourIP6 == nil { if ourIP4 == nil && ourIP6 == nil {
tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"description": "cannot detect public IP address", "description": "cannot detect public IP address",
"error": errors.New("ifconfig.co is unreachable for both IPv4 and IPv6"), "error": errors.New("cannot detect automatically and public-ipv4/public-ipv6 are not set in config"),
}) })
return false return false
} }
+2
View File
@@ -35,6 +35,8 @@ type Config struct {
DomainFrontingProxyProtocol TypeBool `json:"domainFrontingProxyProtocol"` DomainFrontingProxyProtocol TypeBool `json:"domainFrontingProxyProtocol"`
TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"` TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"`
Concurrency TypeConcurrency `json:"concurrency"` Concurrency TypeConcurrency `json:"concurrency"`
PublicIPv4 TypeIP `json:"publicIpv4"`
PublicIPv6 TypeIP `json:"publicIpv6"`
DomainFronting struct { DomainFronting struct {
IP TypeIP `json:"ip"` IP TypeIP `json:"ip"`
Port TypePort `json:"port"` Port TypePort `json:"port"`
+26
View File
@@ -42,6 +42,32 @@ func (suite *ConfigTestSuite) TestParseMinimalConfig() {
suite.Equal("0.0.0.0:3128", conf.BindTo.String()) suite.Equal("0.0.0.0:3128", conf.BindTo.String())
} }
func (suite *ConfigTestSuite) TestParsePublicIP() {
conf, err := config.Parse(suite.ReadConfig("public_ip.toml"))
suite.NoError(err)
suite.Equal("203.0.113.1", conf.PublicIPv4.Get(nil).String())
suite.Equal("2001:db8::1", conf.PublicIPv6.Get(nil).String())
}
func (suite *ConfigTestSuite) TestParsePublicIPv4Only() {
conf, err := config.Parse(suite.ReadConfig("public_ip_v4_only.toml"))
suite.NoError(err)
suite.Equal("203.0.113.1", conf.PublicIPv4.Get(nil).String())
suite.Nil(conf.PublicIPv6.Get(nil))
}
func (suite *ConfigTestSuite) TestParsePublicIPInvalid() {
_, err := config.Parse(suite.ReadConfig("public_ip_invalid.toml"))
suite.Error(err)
}
func (suite *ConfigTestSuite) TestParsePublicIPNotSet() {
conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
suite.NoError(err)
suite.Nil(conf.PublicIPv4.Get(nil))
suite.Nil(conf.PublicIPv6.Get(nil))
}
func (suite *ConfigTestSuite) TestString() { func (suite *ConfigTestSuite) TestString() {
conf, err := config.Parse(suite.ReadConfig("minimal.toml")) conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
suite.NoError(err) suite.NoError(err)
+2
View File
@@ -21,6 +21,8 @@ type tomlConfig struct {
DomainFrontingProxyProtocol bool `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"` DomainFrontingProxyProtocol bool `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"`
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"` TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"` Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
PublicIPv4 string `toml:"public-ipv4" json:"publicIpv4,omitempty"`
PublicIPv6 string `toml:"public-ipv6" json:"publicIpv6,omitempty"`
DomainFronting struct { DomainFronting struct {
IP string `toml:"ip" json:"ip,omitempty"` IP string `toml:"ip" json:"ip,omitempty"`
Port uint `toml:"port" json:"port,omitempty"` Port uint `toml:"port" json:"port,omitempty"`
+4
View File
@@ -0,0 +1,4 @@
secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
bind-to = "0.0.0.0:3128"
public-ipv4 = "203.0.113.1"
public-ipv6 = "2001:db8::1"
+3
View File
@@ -0,0 +1,3 @@
secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
bind-to = "0.0.0.0:3128"
public-ipv4 = "not-an-ip"
+3
View File
@@ -0,0 +1,3 @@
secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
bind-to = "0.0.0.0:3128"
public-ipv4 = "203.0.113.1"