Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion internal/cli/run_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net"
"os"
"strings"

"github.com/9seconds/mtg/v2/antireplay"
"github.com/9seconds/mtg/v2/events"
Expand Down Expand Up @@ -207,7 +208,67 @@ 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
}

for _, addr := range addresses {
if (ourIP4 != nil && addr.IP.String() == ourIP4.String()) ||
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling that both addresses must match. If we have 1 matching IPv6 but IPv4 mismatch, it could lead to major problems, given that most of e2e connectivity is still made with IPv4

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — fixed in 491a355.

The check now requires each detected IP family to be present in the DNS response: a matching AAAA no longer masks a mismatched A, and vice versa. If only one family is detected (e.g. IPv6 not configured), only that family is required. The warning also reports per-family match status (ipv4_match=false / ipv6_match=true) so operators can tell which record is wrong.

Side note: doctor.go:checkSecretHost has the same OR-logic and would have the same blind spot. Happy to align it in this PR for consistency, or split into a follow-up — whichever you prefer.

(ourIP6 != nil && addr.IP.String() == ourIP6.String()) {
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()
}

log.BindStr("hostname", host).
BindStr("resolved", strings.Join(resolved, ", ")).
BindStr("public_ip", our).
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")
Expand All @@ -222,6 +283,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"),
Expand Down
Loading