fix(cli): treat explicit intercept-mode off as final in listener setup

tryUpdateListenerConfig treated an explicit --intercept-mode off the same
as an empty flag and fell back to the persisted config value. run() selects
the listener strategy before it clears the persisted mode, so the first
start after a revert to standard mode selected the intercept strategy from
a stale dns/hard value while setDNS kept interception off.

Extract the resolution into listenerInterceptMode and make an explicit off
final, the same contract as setDNS. Add a regression test that fails
without the fix.
This commit is contained in:
Anthony Wong
2026-08-22 01:34:48 +07:00
committed by Cuong Manh Le
parent 30acb846ca
commit d78e9bcf5b
2 changed files with 46 additions and 5 deletions
+12 -5
View File
@@ -1564,6 +1564,17 @@ func isExplicitInterceptListener(ip string, port int) bool {
return !(ip == "127.0.0.1" && port == 53)
}
// listenerInterceptMode resolves the mode that selects the listener binding
// strategy. An explicit "off" is final here, the same as in setDNS. A fallback
// to the config value would select the intercept strategy from a stale
// persisted mode on the first start after a revert to standard mode.
func listenerInterceptMode(cfg *ctrld.Config) string {
if interceptMode == "" {
return cfg.Service.InterceptMode
}
return interceptMode
}
// tryUpdateListenerConfig tries updating listener config with a working one.
// If fatal is true, and there's listen address conflicted, the function do
// fatal error.
@@ -1573,13 +1584,9 @@ func tryUpdateListenerConfig(cfg *ctrld.Config, infoLogger *zerolog.Logger, noti
// 1. If config has explicit non-default IP:port, use exactly that
// 2. Otherwise: try 127.0.0.1:53, then 127.0.0.1:5354, then fatal
// This bypasses the full cd-mode listener probing loop entirely.
// Check interceptMode (CLI flag) first, then fall back to config value.
// dnsIntercept bool is derived later in prog.run(), but we need to know
// the intercept mode here to select the right listener probing strategy.
im := interceptMode
if im == "" || im == "off" {
im = cfg.Service.InterceptMode
}
im := listenerInterceptMode(cfg)
if (im == "dns" || im == "hard") && runtime.GOOS == "darwin" {
return tryUpdateListenerConfigIntercept(cfg, notifyFunc, fatal)
}
+34
View File
@@ -0,0 +1,34 @@
package cli
import (
"testing"
"github.com/Control-D-Inc/ctrld"
)
func TestListenerInterceptModeExplicitOff(t *testing.T) {
oldIntercept := interceptMode
t.Cleanup(func() { interceptMode = oldIntercept })
cfg := &ctrld.Config{}
cfg.Service.InterceptMode = "dns"
tests := []struct {
name string
flag string
want string
}{
{name: "explicit off is final", flag: "off", want: "off"},
{name: "empty flag falls back to config", flag: "", want: "dns"},
{name: "explicit dns wins over config", flag: "dns", want: "dns"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
interceptMode = tc.flag
if got := listenerInterceptMode(cfg); got != tc.want {
t.Fatalf("listenerInterceptMode() = %q, want %q", got, tc.want)
}
})
}
}