diff --git a/internal/cli/run_proxy.go b/internal/cli/run_proxy.go index 5f0b3dee6..5d9e63e98 100644 --- a/internal/cli/run_proxy.go +++ b/internal/cli/run_proxy.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "os" + "strings" "github.com/9seconds/mtg/v2/antireplay" "github.com/9seconds/mtg/v2/events" @@ -207,7 +208,86 @@ func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStr return events.NewNoopStream(), nil } -func runProxy(conf *config.Config, version string) error { //nolint: funlen +func warnSNIMismatch(conf *config.Config, ntw mtglib.Network, log mtglib.Logger) { + host := conf.Secret.Host + if host == "" { + return + } + + addresses, err := net.DefaultResolver.LookupIPAddr(context.Background(), host) + if err != nil { + log.BindStr("hostname", host). + WarningError("SNI-DNS check: cannot resolve secret hostname", err) + return + } + + ourIP4 := conf.PublicIPv4.Get(nil) + if ourIP4 == nil { + ourIP4 = getIP(ntw, "tcp4") + } + + ourIP6 := conf.PublicIPv6.Get(nil) + if ourIP6 == nil { + ourIP6 = getIP(ntw, "tcp6") + } + + if ourIP4 == nil && ourIP6 == nil { + log.Warning("SNI-DNS check: cannot detect public IP address; set public-ipv4/public-ipv6 in config or run 'mtg doctor'") + return + } + + v4Match := ourIP4 == nil + v6Match := ourIP6 == nil + + for _, addr := range addresses { + if ourIP4 != nil && addr.IP.String() == ourIP4.String() { + v4Match = true + } + + if ourIP6 != nil && addr.IP.String() == ourIP6.String() { + v6Match = true + } + } + + if v4Match && v6Match { + return + } + + resolved := make([]string, 0, len(addresses)) + for _, addr := range addresses { + resolved = append(resolved, addr.IP.String()) + } + + our := "" + if ourIP4 != nil { + our = ourIP4.String() + } + + if ourIP6 != nil { + if our != "" { + our += "/" + } + + our += ourIP6.String() + } + + entry := log.BindStr("hostname", host). + BindStr("resolved", strings.Join(resolved, ", ")). + BindStr("public_ip", our) + + if ourIP4 != nil { + entry = entry.BindStr("ipv4_match", fmt.Sprintf("%t", v4Match)) + } + + if ourIP6 != nil { + entry = entry.BindStr("ipv6_match", fmt.Sprintf("%t", v6Match)) + } + + entry.Warning("SNI-DNS mismatch: secret hostname does not resolve to this server's public IP. " + + "DPI may detect and block the proxy. See 'mtg doctor' for details") +} + +func runProxy(conf *config.Config, version string) error { //nolint: funlen, cyclop logger := makeLogger(conf) logger.BindJSON("configuration", conf.String()).Debug("configuration") @@ -222,6 +302,8 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen return fmt.Errorf("cannot build network: %w", err) } + warnSNIMismatch(conf, ntw, logger) + blocklist, err := makeIPBlocklist( conf.Defense.Blocklist, logger.Named("blocklist"),