diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index b99c48f..1984f70 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -178,7 +178,15 @@ func RunMobile(appConfig *AppConfig, appCallback *AppCallback, stopCh chan struc noConfigStart = false homedir = appConfig.HomeDir verbose = appConfig.Verbose - cdUID = appConfig.CdUID + if appConfig.ProvisionID != "" { + cdOrg = appConfig.ProvisionID + } + if appConfig.CustomHostname != "" { + customHostname = appConfig.CustomHostname + } + if appConfig.CdUID != "" { + cdUID = appConfig.CdUID + } cdUpstreamProto = appConfig.UpstreamProto logPath = appConfig.LogPath run(appCallback, stopCh) diff --git a/cmd/cli/commands.go b/cmd/cli/commands.go index 3733f71..a1074f2 100644 --- a/cmd/cli/commands.go +++ b/cmd/cli/commands.go @@ -189,6 +189,7 @@ func initRunCmd() *cobra.Command { runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) _ = runCmd.Flags().MarkHidden("iface") runCmd.Flags().StringVarP(&cdUpstreamProto, "proto", "", ctrld.ResolverTypeDOH, `Control D upstream type, either "doh" or "doh3"`) + runCmd.Flags().BoolVarP(&rfc1918, "rfc1918", "", false, "Listen on RFC1918 addresses when 127.0.0.1 is the only listener") runCmd.FParseErrWhitelist = cobra.FParseErrWhitelist{UnknownFlags: true} rootCmd.AddCommand(runCmd) @@ -531,6 +532,7 @@ NOTE: running "ctrld start" without any arguments will start already installed c startCmd.Flags().BoolVarP(&skipSelfChecks, "skip_self_checks", "", false, `Skip self checks after installing ctrld service`) startCmd.Flags().BoolVarP(&startOnly, "start_only", "", false, "Do not install new service") _ = startCmd.Flags().MarkHidden("start_only") + startCmd.Flags().BoolVarP(&rfc1918, "rfc1918", "", false, "Listen on RFC1918 addresses when 127.0.0.1 is the only listener") routerCmd := &cobra.Command{ Use: "setup", diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index f1aa445..994741b 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -207,8 +207,8 @@ func (p *prog) serveDNS(listenerNum string) error { return nil }) } - // When we spawn a listener on 127.0.0.1, also spawn listeners on the RFC1918 - // addresses of the machine. So ctrld could receive queries from LAN clients. + // When we spawn a listener on 127.0.0.1, also spawn listeners on the RFC1918 addresses of the machine + // if explicitly set via setting rfc1918 flag, so ctrld could receive queries from LAN clients. if needRFC1918Listeners(listenerConfig) { g.Go(func() error { for _, addr := range ctrld.Rfc1918Addresses() { @@ -1039,7 +1039,7 @@ func (p *prog) queryFromSelf(ip string) bool { // needRFC1918Listeners reports whether ctrld need to spawn listener for RFC 1918 addresses. // This is helpful for non-desktop platforms to receive queries from LAN clients. func needRFC1918Listeners(lc *ctrld.ListenerConfig) bool { - return lc.IP == "127.0.0.1" && lc.Port == 53 && !ctrld.IsDesktopPlatform() + return rfc1918 && lc.IP == "127.0.0.1" && lc.Port == 53 } // ipFromARPA parses a FQDN arpa domain and return the IP address if valid. diff --git a/cmd/cli/library.go b/cmd/cli/library.go index 3c1db1b..7847dd7 100644 --- a/cmd/cli/library.go +++ b/cmd/cli/library.go @@ -18,11 +18,13 @@ type AppCallback struct { // AppConfig allows overwriting ctrld cli flags from mobile platforms. type AppConfig struct { - CdUID string - HomeDir string - UpstreamProto string - Verbose int - LogPath string + CdUID string + ProvisionID string + CustomHostname string + HomeDir string + UpstreamProto string + Verbose int + LogPath string } const ( diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 6a8cb62..0783975 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -39,6 +39,7 @@ var ( skipSelfChecks bool cleanup bool startOnly bool + rfc1918 bool mainLog atomic.Pointer[zerolog.Logger] consoleWriter zerolog.ConsoleWriter diff --git a/cmd/ctrld_library/main.go b/cmd/ctrld_library/main.go index 49f5b26..b2e643d 100644 --- a/cmd/ctrld_library/main.go +++ b/cmd/ctrld_library/main.go @@ -28,15 +28,17 @@ type AppCallback interface { // Start configures utility with config.toml from provided directory. // This function will block until Stop is called // Check port availability prior to calling it. -func (c *Controller) Start(CdUID string, HomeDir string, UpstreamProto string, logLevel int, logPath string) { +func (c *Controller) Start(CdUID string, ProvisionID string, CustomHostname string, HomeDir string, UpstreamProto string, logLevel int, logPath string) { if c.stopCh == nil { c.stopCh = make(chan struct{}) c.Config = cli.AppConfig{ - CdUID: CdUID, - HomeDir: HomeDir, - UpstreamProto: UpstreamProto, - Verbose: logLevel, - LogPath: logPath, + CdUID: CdUID, + ProvisionID: ProvisionID, + CustomHostname: CustomHostname, + HomeDir: HomeDir, + UpstreamProto: UpstreamProto, + Verbose: logLevel, + LogPath: logPath, } appCallback := mapCallback(c.AppCallback) cli.RunMobile(&c.Config, &appCallback, c.stopCh) diff --git a/config_quic.go b/config_quic.go index cadcb6b..33f56b9 100644 --- a/config_quic.go +++ b/config_quic.go @@ -36,7 +36,7 @@ func (uc *UpstreamConfig) setupDOH3Transport() { func (uc *UpstreamConfig) newDOH3Transport(addrs []string) http.RoundTripper { rt := &http3.Transport{} rt.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} - rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { _, port, _ := net.SplitHostPort(addr) // if we have a bootstrap ip set, use it to avoid DNS lookup if uc.BootstrapIP != "" { @@ -96,14 +96,14 @@ func (uc *UpstreamConfig) doh3Transport(dnsType uint16) http.RoundTripper { // - quic dialer is different with net.Dialer // - simplification for quic free version type parallelDialerResult struct { - conn quic.EarlyConnection + conn *quic.Conn err error } type quicParallelDialer struct{} // Dial performs parallel dialing to the given address list. -func (d *quicParallelDialer) Dial(ctx context.Context, addrs []string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { +func (d *quicParallelDialer) Dial(ctx context.Context, addrs []string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { if len(addrs) == 0 { return nil, errors.New("empty addresses") } diff --git a/docs/known-issues.md b/docs/known-issues.md new file mode 100644 index 0000000..0d13bcc --- /dev/null +++ b/docs/known-issues.md @@ -0,0 +1,42 @@ +# Known Issues + +This document outlines known issues with ctrld and their current status, workarounds, and recommendations. + +## macOS (Darwin) Issues + +### Self-Upgrade Issue on Darwin 15.5 + +**Issue**: ctrld self-upgrading functionality may not work on macOS Darwin 15.5. + +**Status**: Under investigation + +**Description**: Users on macOS Darwin 15.5 may experience issues when ctrld attempts to perform automatic self-upgrades. The upgrade process would be triggered, but ctrld won't be upgraded. + +**Workarounds**: +1. **Recommended**: Upgrade your macOS system to Darwin 15.6 or later, which has been tested and verified to work correctly with ctrld self-upgrade functionality. +2. **Alternative**: Run `ctrld upgrade prod` directly to manually upgrade ctrld to the latest version on Darwin 15.5. + +**Affected Versions**: ctrld v1.4.2 and later on macOS Darwin 15.5 + +**Last Updated**: 05/09/2025 + +--- + +## Contributing to Known Issues + +If you encounter an issue not listed here, please: + +1. Check the [GitHub Issues](https://github.com/Control-D-Inc/ctrld/issues) to see if it's already reported +2. If not reported, create a new issue with: + - Detailed description of the problem + - Steps to reproduce + - Expected vs actual behavior + - System information (OS, version, architecture) + - ctrld version + +## Issue Status Legend + +- **Under investigation**: Issue is confirmed and being analyzed +- **Workaround available**: Temporary solution exists while permanent fix is developed +- **Fixed**: Issue has been resolved in a specific version +- **Won't fix**: Issue is acknowledged but will not be addressed due to technical limitations or design decisions diff --git a/doq_test.go b/doq_test.go index 430a22a..14055dd 100644 --- a/doq_test.go +++ b/doq_test.go @@ -142,7 +142,7 @@ func (s *testQUICServer) serve(t *testing.T) { } // handleConnection manages an individual QUIC connection by accepting and handling incoming streams in separate goroutines. -func (s *testQUICServer) handleConnection(t *testing.T, conn quic.Connection) { +func (s *testQUICServer) handleConnection(t *testing.T, conn *quic.Conn) { for { stream, err := conn.AcceptStream(context.Background()) if err != nil { @@ -154,7 +154,7 @@ func (s *testQUICServer) handleConnection(t *testing.T, conn quic.Connection) { } // handleStream processes a single QUIC stream, reads DNS messages, generates a response, and sends it back to the client. -func (s *testQUICServer) handleStream(t *testing.T, stream quic.Stream) { +func (s *testQUICServer) handleStream(t *testing.T, stream *quic.Stream) { defer stream.Close() // Read length (2 bytes) diff --git a/go.mod b/go.mod index 1d94a07..2280eb6 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.5.0 github.com/prometheus/prom2json v1.3.3 - github.com/quic-go/quic-go v0.48.2 + github.com/quic-go/quic-go v0.54.0 github.com/rs/zerolog v1.28.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 @@ -54,10 +54,8 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -74,7 +72,6 @@ require ( github.com/mdlayher/packet v1.1.2 // indirect github.com/mdlayher/socket v0.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -89,7 +86,7 @@ require ( github.com/subosito/gotenv v1.4.2 // indirect github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect github.com/vishvananda/netns v0.0.4 // indirect - go.uber.org/mock v0.4.0 // indirect + go.uber.org/mock v0.5.0 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/crypto v0.36.0 // indirect diff --git a/go.sum b/go.sum index 25af133..56a71e1 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -103,8 +101,6 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= @@ -162,8 +158,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -242,10 +236,6 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -271,8 +261,8 @@ github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcET github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= -github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -330,8 +320,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= @@ -505,8 +495,6 @@ golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/nameservers_linux.go b/nameservers_linux.go index 13a5507..37a9ed2 100644 --- a/nameservers_linux.go +++ b/nameservers_linux.go @@ -5,9 +5,12 @@ import ( "bytes" "encoding/hex" "net" + "net/netip" "os" "strings" + "tailscale.com/net/netmon" + "github.com/Control-D-Inc/ctrld/internal/dns/resolvconffile" ) @@ -128,3 +131,25 @@ func virtualInterfaces() set { } return s } + +// validInterfacesMap returns a set containing non virtual interfaces. +// TODO: deduplicated with cmd/cli/net_linux.go in v2. +func validInterfaces() set { + m := make(map[string]struct{}) + vis := virtualInterfaces() + netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) { + if _, existed := vis[i.Name]; existed { + return + } + m[i.Name] = struct{}{} + }) + // Fallback to default route interface if found nothing. + if len(m) == 0 { + defaultRoute, err := netmon.DefaultRoute() + if err != nil { + return m + } + m[defaultRoute.InterfaceName] = struct{}{} + } + return m +} diff --git a/net_darwin.go b/net_darwin.go new file mode 100644 index 0000000..5b01e9f --- /dev/null +++ b/net_darwin.go @@ -0,0 +1,35 @@ +package ctrld + +import ( + "bufio" + "bytes" + "io" + "os/exec" + "strings" +) + +// validInterfaces returns a set of all valid hardware ports. +// TODO: deduplicated with cmd/cli/net_darwin.go in v2. +func validInterfaces() map[string]struct{} { + b, err := exec.Command("networksetup", "-listallhardwareports").Output() + if err != nil { + return nil + } + return parseListAllHardwarePorts(bytes.NewReader(b)) +} + +// parseListAllHardwarePorts parses output of "networksetup -listallhardwareports" +// and returns map presents all hardware ports. +func parseListAllHardwarePorts(r io.Reader) map[string]struct{} { + m := make(map[string]struct{}) + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + after, ok := strings.CutPrefix(line, "Device: ") + if !ok { + continue + } + m[after] = struct{}{} + } + return m +} diff --git a/cmd/cli/net_darwin_test.go b/net_darwin_test.go similarity index 99% rename from cmd/cli/net_darwin_test.go rename to net_darwin_test.go index 9ef1906..8f9734f 100644 --- a/cmd/cli/net_darwin_test.go +++ b/net_darwin_test.go @@ -1,4 +1,4 @@ -package cli +package ctrld import ( "maps" diff --git a/net_others.go b/net_others.go new file mode 100644 index 0000000..ae7ab8e --- /dev/null +++ b/net_others.go @@ -0,0 +1,15 @@ +//go:build !darwin && !windows && !linux + +package ctrld + +import "tailscale.com/net/netmon" + +// validInterfaces returns a set containing only default route interfaces. +// TODO: deuplicated with cmd/cli/net_others.go in v2. +func validInterfaces() map[string]struct{} { + defaultRoute, err := netmon.DefaultRoute() + if err != nil { + return nil + } + return map[string]struct{}{defaultRoute.InterfaceName: {}} +} diff --git a/resolver.go b/resolver.go index 27c0108..3aeddd0 100644 --- a/resolver.go +++ b/resolver.go @@ -729,10 +729,15 @@ func newResolverWithNameserver(nameservers []string) *osResolver { return r } -// Rfc1918Addresses returns the list of local interfaces private IP addresses +// Rfc1918Addresses returns the list of local physical interfaces private IP addresses func Rfc1918Addresses() []string { + vis := validInterfaces() var res []string netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) { + // Skip virtual interfaces. + if _, existed := vis[i.Name]; !existed { + return + } addrs, _ := i.Addrs() for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet)