From edca1f4f899ee8cc26e7c1f405a3c56abc658e66 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 24 Jan 2024 15:35:14 +0700 Subject: [PATCH 01/27] Drop quic free build Since go1.21, Go standard library have added support for QUIC protocol. The binary size gains between quic and quic-free version is now minimal. Removing the quic free build, simplify the code and build process. --- README.md | 2 +- config_quic.go | 2 -- config_quic_free.go | 9 --------- go.mod | 11 +++++------ go.sum | 25 ++++++++++--------------- scripts/build.sh | 18 ------------------ 6 files changed, 16 insertions(+), 51 deletions(-) delete mode 100644 config_quic_free.go diff --git a/README.md b/README.md index 4c7db37..eddcb41 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ $ docker pull controldns/ctrld Alternatively, if you know what you're doing you can download pre-compiled binaries from the [Releases](https://github.com/Control-D-Inc/ctrld/releases) section for the appropriate platform. ## Build -Lastly, you can build `ctrld` from source which requires `go1.20+`: +Lastly, you can build `ctrld` from source which requires `go1.21+`: ```shell $ go build ./cmd/ctrld diff --git a/config_quic.go b/config_quic.go index 5103231..a6dd8b7 100644 --- a/config_quic.go +++ b/config_quic.go @@ -1,5 +1,3 @@ -//go:build !qf - package ctrld import ( diff --git a/config_quic_free.go b/config_quic_free.go deleted file mode 100644 index a674a1b..0000000 --- a/config_quic_free.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build qf - -package ctrld - -import "net/http" - -func (uc *UpstreamConfig) setupDOH3Transport() {} - -func (uc *UpstreamConfig) doh3Transport(dnsType uint16) http.RoundTripper { return nil } diff --git a/go.mod b/go.mod index ebb62d2..0476717 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Control-D-Inc/ctrld -go 1.20 +go 1.21 require ( github.com/coreos/go-systemd/v22 v22.5.0 @@ -20,8 +20,9 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/pelletier/go-toml/v2 v2.0.8 github.com/prometheus/client_golang v1.15.1 + github.com/prometheus/client_model v0.4.0 github.com/prometheus/prom2json v1.3.3 - github.com/quic-go/quic-go v0.38.0 + github.com/quic-go/quic-go v0.41.0 github.com/rs/zerolog v1.28.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -43,7 +44,6 @@ require ( 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/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect @@ -66,11 +66,9 @@ require ( github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.3.2 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/spf13/afero v1.9.5 // indirect @@ -79,10 +77,11 @@ require ( github.com/subosito/gotenv v1.4.2 // indirect github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect github.com/vishvananda/netns v0.0.4 // indirect + go.uber.org/mock v0.3.0 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect - golang.org/x/mod v0.10.0 // indirect + golang.org/x/mod v0.11.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.9.1 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index bbfe6c7..6ab5340 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,7 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ= +github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -78,6 +79,7 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -102,8 +104,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -162,6 +162,7 @@ github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= @@ -228,6 +229,7 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 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= @@ -251,10 +253,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.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= -github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= -github.com/quic-go/quic-go v0.38.0 h1:T45lASr5q/TrVwt+jrVccmqHhPL2XuSyoCLVCpfOSLc= -github.com/quic-go/quic-go v0.38.0/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg= +github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k= +github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA= 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= @@ -304,13 +304,14 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 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.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -358,9 +359,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -393,7 +393,6 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= @@ -416,7 +415,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -456,10 +454,8 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -532,7 +528,6 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/scripts/build.sh b/scripts/build.sh index 6c96f0f..567b745 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -74,15 +74,6 @@ build() { fi GOOS=${goos} GOARCH=${goarch} GOARM=${3} "$go" build -ldflags="$ldflags" -o "$binary" ./cmd/ctrld compress "$binary" - - if [ -z "${CTRLD_NO_QF}" ]; then - binary_qf=${executable_name}-qf-${goos}-${goarch}v${3} - if [ "$CGO_ENABLED" = "0" ]; then - binary_qf=${binary_qf}-nocgo - fi - GOOS=${goos} GOARCH=${goarch} GOARM=${3} "$go" build -ldflags="$ldflags" -tags=qf -o "$binary_qf" ./cmd/ctrld - compress "$binary_qf" - fi ;; *) # GOMIPS is required for linux/mips: https://nileshgr.com/2020/02/16/golang-on-openwrt-mips/ @@ -92,15 +83,6 @@ build() { fi GOOS=${goos} GOARCH=${goarch} GOMIPS=softfloat "$go" build -ldflags="$ldflags" -o "$binary" ./cmd/ctrld compress "$binary" - - if [ -z "${CTRLD_NO_QF}" ]; then - binary_qf=${executable_name}-qf-${goos}-${goarch} - if [ "$CGO_ENABLED" = "0" ]; then - binary_qf=${binary_qf}-nocgo - fi - GOOS=${goos} GOARCH=${goarch} GOMIPS=softfloat "$go" build -ldflags="$ldflags" -tags=qf -o "$binary_qf" ./cmd/ctrld - compress "$binary_qf" - fi ;; esac } From 99651f6e5bc58f489d896ee4168311a83fb5db9d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 24 Jan 2024 23:15:12 +0700 Subject: [PATCH 02/27] internal/router: supports UniFi UXG products --- internal/router/router.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/router/router.go b/internal/router/router.go index b8a414b..2990cd7 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -199,7 +199,7 @@ func distroName() string { return merlin.Name case haveFile("/etc/openwrt_version"): return openwrt.Name - case haveDir("/data/unifi"): + case isUbios(): return ubios.Name case bytes.HasPrefix(unameU(), []byte("synology")): return synology.Name @@ -234,3 +234,14 @@ func unameU() []byte { out, _ := exec.Command("uname", "-u").Output() return out } + +// isUbios reports whether the current machine is running on Ubios. +func isUbios() bool { + if haveDir("/data/unifi") { + return true + } + if err := exec.Command("ubnt-device-info", "firmware").Run(); err == nil { + return true + } + return false +} From 49441f62f3d08ae3797f7d95a3a438bd85eba8eb Mon Sep 17 00:00:00 2001 From: Yegor Sak Date: Thu, 25 Jan 2024 17:28:23 +0000 Subject: [PATCH 03/27] Update file config.md --- docs/config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.md b/docs/config.md index 4f4d897..404606d 100644 --- a/docs/config.md +++ b/docs/config.md @@ -242,7 +242,7 @@ If set to `true`, collect and export the query counters, and show them in `clien - Default: false ### metrics_listener -Specifying the `ip` and `port` of the metrics server. +Specifying the `ip` and `port` of the Prometheus metrics server. The Prometheus metrics will be available on: `http://ip:port/metrics`. You can also append `/metrics/json` to get the same data in json format. - Type: string - Required: no From 5d65416227637b99cd947f3f437d8efeaf7fff30 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 25 Jan 2024 17:48:34 +0700 Subject: [PATCH 04/27] internal/clientinfo: fill empty hostname based on MAC address An interface may have multiple MAC addresses, that leads to the problem when looking up hostname for its multiple pairs, because the "ip" map, which storing "mac => ip" mapping can only store 1 entry. It ends up returns an empty hostname for a known MAC address. Fixing this by filling empty hostname based on clients which is already listed, ensuring all clients with the same MAC address will have the same hostname information. --- internal/clientinfo/client_info.go | 7 +++++++ internal/clientinfo/client_info_test.go | 28 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/internal/clientinfo/client_info.go b/internal/clientinfo/client_info.go index 7f41fca..1a775ea 100644 --- a/internal/clientinfo/client_info.go +++ b/internal/clientinfo/client_info.go @@ -384,6 +384,7 @@ func (t *Table) ListClients() []*Client { } } } + clientsByMAC := make(map[string]*Client) for ip := range ipMap { c := ipMap[ip] for _, e := range t.lookupMacAll(ip) { @@ -397,6 +398,7 @@ func (t *Table) ListClients() []*Client { for _, e := range t.lookupHostnameAll(ip, c.Mac) { if c.Hostname == "" && e.name != "" { c.Hostname = e.name + clientsByMAC[c.Mac] = c } if e.name != "" { c.Source[e.src] = struct{}{} @@ -405,6 +407,11 @@ func (t *Table) ListClients() []*Client { } clients := make([]*Client, 0, len(ipMap)) for _, c := range ipMap { + // If we found a client with empty hostname, use hostname from + // an existed client which has the same MAC address. + if cFromMac := clientsByMAC[c.Mac]; cFromMac != nil && c.Hostname == "" { + c.Hostname = cFromMac.Hostname + } clients = append(clients, c) } return clients diff --git a/internal/clientinfo/client_info_test.go b/internal/clientinfo/client_info_test.go index e6575f2..b5bdfa5 100644 --- a/internal/clientinfo/client_info_test.go +++ b/internal/clientinfo/client_info_test.go @@ -44,3 +44,31 @@ func TestTable_LookupRFC1918IPv4(t *testing.T) { t.Fatalf("unexpected result, want: %s, got: %s", rfc1918IPv4, got) } } + +func TestTable_ListClients(t *testing.T) { + mac := "74:56:3c:44:eb:5e" + ipv6_1 := "2405:4803:a04b:4190:fbe9:cd14:d522:bbae" + ipv6_2 := "2405:4803:a04b:4190:fbe9:cd14:d522:bbab" + table := &Table{} + + // NDP init. + table.ndp = &ndpDiscover{} + table.ndp.mac.Store(ipv6_1, mac) + table.ndp.mac.Store(ipv6_2, mac) + table.ndp.ip.Store(mac, ipv6_1) + table.ndp.ip.Store(mac, ipv6_2) + table.ipResolvers = append(table.ipResolvers, table.ndp) + table.macResolvers = append(table.macResolvers, table.ndp) + + hostname := "foo" + // mdns init. + table.mdns = &mdns{} + table.mdns.name.Store(ipv6_2, hostname) + table.hostnameResolvers = append(table.hostnameResolvers, table.mdns) + + for _, c := range table.ListClients() { + if c.Hostname != hostname { + t.Fatalf("missing hostname for client: %v", c) + } + } +} From 67d74774a99a2ed5ce2e9c35283b2ddfecbb9f9d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 26 Jan 2024 15:33:59 +0700 Subject: [PATCH 05/27] all: include file information in Windows builds --- .gitignore | 9 +++++++++ cmd/cli/winres/winres.json | 20 ++++++++++++++++++++ cmd/cli/winres_windows.go | 4 ++++ scripts/build.sh | 2 ++ 4 files changed, 35 insertions(+) create mode 100644 cmd/cli/winres/winres.json create mode 100644 cmd/cli/winres_windows.go diff --git a/.gitignore b/.gitignore index 4816731..8e70cc6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,12 @@ gon.hcl /Build .DS_Store + +# Release folder +dist/ + +# Binaries +ctrld-* + +# generated file +cmd/cli/rsrc_*.syso diff --git a/cmd/cli/winres/winres.json b/cmd/cli/winres/winres.json new file mode 100644 index 0000000..fd12759 --- /dev/null +++ b/cmd/cli/winres/winres.json @@ -0,0 +1,20 @@ +{ + "RT_VERSION": { + "#1": { + "0000": { + "fixed": { + "file_version": "0.0.0.1" + }, + "info": { + "0409": { + "CompanyName": "ControlD Inc", + "FileDescription": "Control D DNS daemon", + "ProductName": "ctrld", + "InternalName": "ctrld", + "LegalCopyright": "ControlD Inc 2024" + } + } + } + } + } +} diff --git a/cmd/cli/winres_windows.go b/cmd/cli/winres_windows.go new file mode 100644 index 0000000..30ebd95 --- /dev/null +++ b/cmd/cli/winres_windows.go @@ -0,0 +1,4 @@ +//go:generate go-winres make --product-version=git-tag --file-version=git-tag +package cli + +// Placeholder file for windows builds. diff --git a/scripts/build.sh b/scripts/build.sh index 567b745..2faeddc 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -72,6 +72,7 @@ build() { if [ "$CGO_ENABLED" = "0" ]; then binary=${binary}-nocgo fi + GOOS=${goos} GOARCH=${goarch} GOARM=${3} "$go" generate ./... GOOS=${goos} GOARCH=${goarch} GOARM=${3} "$go" build -ldflags="$ldflags" -o "$binary" ./cmd/ctrld compress "$binary" ;; @@ -81,6 +82,7 @@ build() { if [ "$CGO_ENABLED" = "0" ]; then binary=${binary}-nocgo fi + GOOS=${goos} GOARCH=${goarch} GOMIPS=softfloat "$go" generate ./... GOOS=${goos} GOARCH=${goarch} GOMIPS=softfloat "$go" build -ldflags="$ldflags" -o "$binary" ./cmd/ctrld compress "$binary" ;; From 082667180991289f14adcb6bb4cf858496284bc2 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 26 Jan 2024 22:22:31 +0700 Subject: [PATCH 06/27] cmd/cli: set DNS for all physical interfaces on Windows/Darwin --- cmd/cli/net_darwin.go | 18 ++++++++++++ cmd/cli/net_others.go | 2 ++ cmd/cli/prog.go | 66 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/cmd/cli/net_darwin.go b/cmd/cli/net_darwin.go index f456327..37f8d7b 100644 --- a/cmd/cli/net_darwin.go +++ b/cmd/cli/net_darwin.go @@ -42,3 +42,21 @@ func networkServiceName(ifaceName string, r io.Reader) string { } return "" } + +// validInterface reports whether the *net.Interface is a valid one, which includes: +// +// - en0: physical wireless +// - en1: Thunderbolt 1 +// - en2: Thunderbolt 2 +// - en3: Thunderbolt 3 +// - en4: Thunderbolt 4 +// +// For full list, see: https://unix.stackexchange.com/questions/603506/what-are-these-ifconfig-interfaces-on-macos +func validInterface(iface *net.Interface) bool { + switch iface.Name { + case "en0", "en1", "en2", "en3", "en4": + return true + default: + return false + } +} diff --git a/cmd/cli/net_others.go b/cmd/cli/net_others.go index 2f7aec8..c27a608 100644 --- a/cmd/cli/net_others.go +++ b/cmd/cli/net_others.go @@ -5,3 +5,5 @@ package cli import "net" func patchNetIfaceName(iface *net.Interface) error { return nil } + +func validInterface(iface *net.Interface) bool { return true } diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 4b3968f..8d0cf3e 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -13,6 +13,7 @@ import ( "runtime" "sort" "strconv" + "strings" "sync" "syscall" @@ -417,8 +418,14 @@ func (p *prog) setDNS() { if iface == "" { return } + // allIfaces tracks whether we should set DNS for all physical interfaces. + allIfaces := false if iface == "auto" { iface = defaultIfaceName() + // If iface is "auto", it means user does not specify "--iface" flag. + // In this case, ctrld has to set DNS for all physical interfaces, so + // thing will still work when user switch from one to the other. + allIfaces = requiredMultiNICsConfig() } lc := cfg.FirstListener() if lc == nil { @@ -460,14 +467,22 @@ func (p *prog) setDNS() { return } logger.Debug().Msg("setting DNS successfully") + if allIfaces { + withEachPhysicalInterfaces(netIface.Name, "set DNS", func(i *net.Interface) error { + return setDNS(i, nameservers) + }) + } } func (p *prog) resetDNS() { if iface == "" { return } + allIfaces := false if iface == "auto" { iface = defaultIfaceName() + // See corresponding comments in (*prog).setDNS function. + allIfaces = requiredMultiNICsConfig() } logger := mainLog.Load().With().Str("iface", iface).Logger() netIface, err := netInterface(iface) @@ -485,6 +500,9 @@ func (p *prog) resetDNS() { return } logger.Debug().Msg("Restoring DNS successfully") + if allIfaces { + withEachPhysicalInterfaces(netIface.Name, "reset DNS", resetDNS) + } } func randomLocalIP() string { @@ -649,3 +667,51 @@ func canBeLocalUpstream(addr string) bool { } return false } + +// withEachPhysicalInterfaces runs the function f with each physical interfaces, excluding +// the interface that matches excludeIfaceName. The context is used to clarify the +// log message when error happens. +func withEachPhysicalInterfaces(excludeIfaceName, context string, f func(i *net.Interface) error) { + interfaces.ForeachInterface(func(i interfaces.Interface, prefixes []netip.Prefix) { + // Skip not-running/local/virtual interface. + if !i.IsUp() || i.IsLoopback() || len(i.HardwareAddr) == 0 { + return + } + // Skip non-configured interfaces. + if addrs, _ := i.Addrs(); len(addrs) == 0 { + return + } + // Skip invalid interface. + if !validInterface(i.Interface) { + return + } + netIface := i.Interface + if err := patchNetIfaceName(netIface); err != nil { + mainLog.Load().Debug().Err(err).Msg("failed to patch net interface name") + return + } + // Skip excluded interface. + if netIface.Name == excludeIfaceName { + return + } + // Skip Windows Hyper-V Default Switch. + if strings.Contains(netIface.Name, "vEthernet") { + return + } + if err := f(netIface); err != nil { + mainLog.Load().Warn().Err(err).Msgf("failed to %s for interface: %q", context, i.Name) + } else { + mainLog.Load().Debug().Msgf("%s for interface %q successfully", context, i.Name) + } + }) +} + +// requiredMultiNicConfig reports whether ctrld needs to set/reset DNS for multiple NICs. +func requiredMultiNICsConfig() bool { + switch runtime.GOOS { + case "windows", "darwin": + return true + default: + return false + } +} From d822bf425797cacbefdae7f036be13bfa856fa2c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 1 Feb 2024 20:43:03 +0700 Subject: [PATCH 07/27] all: add pin protected deactivation --- cmd/cli/cli.go | 53 +++++++++++++++++++++++++++++++++++++ cmd/cli/control_client.go | 5 ++++ cmd/cli/control_server.go | 28 +++++++++++++++++--- cmd/cli/main.go | 1 + internal/controld/config.go | 5 ++-- 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 57761f2..dd1af17 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -364,6 +364,9 @@ func initCLI() { return } initLogging() + if err := checkDeactivationPin(s); errors.Is(err, errInvalidDeactivationPin) { + os.Exit(deactivationPinInvalidExitCode) + } if doTasks([]task{{s.Stop, true}}) { p.router.Cleanup() p.resetDNS() @@ -372,6 +375,8 @@ func initCLI() { }, } stopCmd.Flags().StringVarP(&iface, "iface", "", "", `Reset DNS setting for iface, "auto" means the default interface gateway`) + stopCmd.Flags().Int64VarP(&deactivationPin, "pin", "", defaultDeactivationPin, `Pin code for stopping ctrld`) + _ = stopCmd.Flags().MarkHidden("pin") restartCmd := &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { @@ -518,10 +523,15 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, if iface == "" { iface = "auto" } + if err := checkDeactivationPin(s); errors.Is(err, errInvalidDeactivationPin) { + os.Exit(deactivationPinInvalidExitCode) + } uninstall(p, s) }, } uninstallCmd.Flags().StringVarP(&iface, "iface", "", "", `Reset DNS setting for iface, use "auto" for the default gateway interface`) + uninstallCmd.Flags().Int64VarP(&deactivationPin, "pin", "", defaultDeactivationPin, `Pin code for uninstalling ctrld`) + _ = uninstallCmd.Flags().MarkHidden("pin") listIfacesCmd := &cobra.Command{ Use: "list", @@ -1171,6 +1181,18 @@ func processNoConfigFlags(noConfigStart bool) { v.Set("upstream", upstream) } +// defaultDeactivationPin is the default value for cdDeactivationPin. +// If cdDeactivationPin equals to this default, it means the pin code is not set from Control D API. +const defaultDeactivationPin = -1 + +// cdDeactivationPin is used in cd mode to decide whether stop and uninstall commands can be run. +var cdDeactivationPin int64 = defaultDeactivationPin + +// deactivationPinNotSet reports whether cdDeactivationPin was not set by processCDFlags. +func deactivationPinNotSet() bool { + return cdDeactivationPin == defaultDeactivationPin +} + func processCDFlags(cfg *ctrld.Config) error { logger := mainLog.Load().With().Str("mode", "cd").Logger() logger.Info().Msgf("fetching Controld D configuration from API: %s", cdUID) @@ -1195,6 +1217,11 @@ func processCDFlags(cfg *ctrld.Config) error { return err } + if resolverConfig.DeactivationPin != nil { + logger.Debug().Msg("saving deactivation pin") + cdDeactivationPin = *resolverConfig.DeactivationPin + } + logger.Info().Msg("generating ctrld config from Control-D configuration") *cfg = ctrld.Config{} @@ -2049,3 +2076,29 @@ func noticeWritingControlDConfig() error { } return nil } + +// deactivationPinInvalidExitCode indicates exit code due to invalid pin code. +const deactivationPinInvalidExitCode = 126 + +// errInvalidDeactivationPin indicates that the deactivation pin is invalid. +var errInvalidDeactivationPin = errors.New("deactivation pin is invalid") + +// checkDeactivationPin validates if the deactivation pin matches one in ControlD config. +func checkDeactivationPin(s service.Service) error { + dir, err := socketDir() + if err != nil { + mainLog.Load().Err(err).Msg("could not check deactivation pin") + return err + } + cc := newSocketControlClient(s, dir) + if cc == nil { + return nil // ctrld is not running. + } + data, _ := json.Marshal(&deactivationRequest{Pin: deactivationPin}) + resp, _ := cc.post(deactivationPath, bytes.NewReader(data)) + if resp != nil && resp.StatusCode == http.StatusOK { + return nil // valid pin + } + mainLog.Load().Error().Msg("deactivation pin is invalid") + return errInvalidDeactivationPin +} diff --git a/cmd/cli/control_client.go b/cmd/cli/control_client.go index c626602..73002e8 100644 --- a/cmd/cli/control_client.go +++ b/cmd/cli/control_client.go @@ -27,3 +27,8 @@ func newControlClient(addr string) *controlClient { func (c *controlClient) post(path string, data io.Reader) (*http.Response, error) { return c.c.Post("http://unix"+path, contentTypeJson, data) } + +// deactivationRequest represents request for validating deactivation pin. +type deactivationRequest struct { + Pin int64 `json:"pin"` +} diff --git a/cmd/cli/control_server.go b/cmd/cli/control_server.go index 36749c1..117174d 100644 --- a/cmd/cli/control_server.go +++ b/cmd/cli/control_server.go @@ -16,10 +16,11 @@ import ( ) const ( - contentTypeJson = "application/json" - listClientsPath = "/clients" - startedPath = "/started" - reloadPath = "/reload" + contentTypeJson = "application/json" + listClientsPath = "/clients" + startedPath = "/started" + reloadPath = "/reload" + deactivationPath = "/deactivation" ) type controlServer struct { @@ -146,6 +147,25 @@ func (p *prog) registerControlServerHandler() { // Otherwise, reload is done. w.WriteHeader(http.StatusOK) })) + p.cs.register(deactivationPath, http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) { + // Non-cd mode or pin code not set, always allowing deactivation. + if cdUID == "" || deactivationPinNotSet() { + w.WriteHeader(http.StatusOK) + return + } + + var req deactivationRequest + if err := json.NewDecoder(request.Body).Decode(&req); err != nil { + w.WriteHeader(http.StatusPreconditionFailed) + mainLog.Load().Err(err).Msg("invalid deactivation request") + return + } + code := http.StatusBadRequest + if req.Pin == cdDeactivationPin { + code = http.StatusOK + } + w.WriteHeader(code) + })) } func jsonResponse(next http.Handler) http.Handler { diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 3f1ef8b..9c64aa0 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -34,6 +34,7 @@ var ( ifaceStartStop string nextdns string cdUpstreamProto string + deactivationPin int64 mainLog atomic.Pointer[zerolog.Logger] consoleWriter zerolog.ConsoleWriter diff --git a/internal/controld/config.go b/internal/controld/config.go index 4cc6770..c095c0c 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -35,8 +35,9 @@ type ResolverConfig struct { Ctrld struct { CustomConfig string `json:"custom_config"` } `json:"ctrld"` - Exclude []string `json:"exclude"` - UID string `json:"uid"` + Exclude []string `json:"exclude"` + UID string `json:"uid"` + DeactivationPin *int64 `json:"deactivation_pin,omitempty"` } type utilityResponse struct { From 9515db7faf2bbcfdb0456ec69939ee833c51cf18 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 3 Feb 2024 00:35:57 +0700 Subject: [PATCH 08/27] cmd/cli: ensure ctrld was uninstalled before installing In some old Windows systems, s.Uninstall does not remove the service completely at the time s.Install was running, prevent ctrld from being installed again. Workaround this by attempting to uninstall ctrld several times, re-check for service status after each attempt to ensure it was uninstalled. --- cmd/cli/cli.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index dd1af17..d5ca749 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -266,7 +266,7 @@ func initCLI() { tasks := []task{ {s.Stop, false}, {func() error { return doGenerateNextDNSConfig(nextdns) }, true}, - {s.Uninstall, false}, + {func() error { return ensureUninstall(s) }, false}, {s.Install, false}, {s.Start, true}, // Note that startCmd do not actually write ControlD config, but the config file was @@ -2102,3 +2102,17 @@ func checkDeactivationPin(s service.Service) error { mainLog.Load().Error().Msg("deactivation pin is invalid") return errInvalidDeactivationPin } + +// ensureUninstall ensures that s.Uninstall will remove ctrld service from system completely. +func ensureUninstall(s service.Service) error { + maxAttempts := 10 + var err error + for i := 0; i < maxAttempts; i++ { + err = s.Uninstall() + if _, err := s.Status(); errors.Is(err, service.ErrNotInstalled) { + return nil + } + time.Sleep(time.Second) + } + return errors.Join(err, errors.New("uninstall failed")) +} From faa0ed06b6f3294228e8abaed4f55d027e2bb0af Mon Sep 17 00:00:00 2001 From: Ginder Singh Date: Wed, 7 Feb 2024 01:36:42 -0500 Subject: [PATCH 09/27] Added pin protection to mobile lib. --- cmd/cli/cli.go | 16 +++++++++++++++- cmd/ctrld_library/main.go | 8 ++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index d5ca749..91ae1fa 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -802,6 +802,15 @@ func RunMobile(appConfig *AppConfig, appCallback *AppCallback, stopCh chan struc run(appCallback, stopCh) } +// CheckDeactivationPin checks if deactivation pin is valid +func CheckDeactivationPin(pin int64) int { + deactivationPin = pin + if err := checkDeactivationPin(nil); errors.Is(err, errInvalidDeactivationPin) { + return deactivationPinInvalidExitCode + } + return 0 +} + // run runs ctrld cli with given app callback and stop channel. func run(appCallback *AppCallback, stopCh chan struct{}) { if stopCh == nil { @@ -2090,7 +2099,12 @@ func checkDeactivationPin(s service.Service) error { mainLog.Load().Err(err).Msg("could not check deactivation pin") return err } - cc := newSocketControlClient(s, dir) + var cc *controlClient + if s == nil { + cc = newControlClient(filepath.Join(dir, ctrldControlUnixSock)) + } else { + cc = newSocketControlClient(s, dir) + } if cc == nil { return nil // ctrld is not running. } diff --git a/cmd/ctrld_library/main.go b/cmd/ctrld_library/main.go index 9bcc151..ec42b9c 100644 --- a/cmd/ctrld_library/main.go +++ b/cmd/ctrld_library/main.go @@ -61,13 +61,13 @@ func mapCallback(callback AppCallback) cli.AppCallback { } } -func (c *Controller) Stop() bool { - if c.stopCh != nil { +func (c *Controller) Stop(Pin int64) int { + errorCode := cli.CheckDeactivationPin(Pin) + if errorCode == 0 && c.stopCh != nil { close(c.stopCh) c.stopCh = nil - return true } - return false + return errorCode } func (c *Controller) IsRunning() bool { From 176c22f229e9b30337beb00ebefd247ff70b1f47 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 2 Feb 2024 22:48:40 +0700 Subject: [PATCH 10/27] cmd/cli: handle general failure better during self check After installing as a system service, "ctrld start" does an end-to-end test for ensuring DNS can be resolved correctly. However, in case the system is mis-configured (by firewall, other softwares ...) and the test query could not be sent to ctrld listener, the current error message is not helpful, causing the confusion from users perspective. To improve this, selfCheckStatus function now returns the actual status and error during its process. The caller can now rely on the service status and the error to produce more useful/friendly message to users. --- cmd/cli/cli.go | 90 +++++++++++++++++++++++++++++++++++-------------- cmd/cli/prog.go | 9 +++++ 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 91ae1fa..0aa22df 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -50,9 +50,10 @@ var ( ) var ( - v = viper.NewWithOptions(viper.KeyDelimiter("::")) - defaultConfigFile = "ctrld.toml" - rootCertPool *x509.CertPool + v = viper.NewWithOptions(viper.KeyDelimiter("::")) + defaultConfigFile = "ctrld.toml" + rootCertPool *x509.CertPool + errSelfCheckNoAnswer = errors.New("no answer from ctrld listener") ) var basicModeFlags = []string{"listen", "primary_upstream", "secondary_upstream", "domains"} @@ -280,17 +281,40 @@ func initCLI() { return } - status := selfCheckStatus(s) - switch status { - case service.StatusRunning: + ok, status, err := selfCheckStatus(s) + switch { + case ok && status == service.StatusRunning: mainLog.Load().Notice().Msg("Service started") default: marker := bytes.Repeat([]byte("="), 32) - mainLog.Load().Error().Msg("ctrld service may not have started due to an error or misconfiguration, service log:") - _, _ = mainLog.Load().Write(marker) - for msg := range runCmdLogCh { - _, _ = mainLog.Load().Write([]byte(msg)) + // If ctrld service is not running, emitting log obtained from ctrld process. + if status != service.StatusRunning { + mainLog.Load().Error().Msg("ctrld service may not have started due to an error or misconfiguration, service log:") + _, _ = mainLog.Load().Write(marker) + haveLog := false + for msg := range runCmdLogCh { + _, _ = mainLog.Load().Write([]byte(msg)) + haveLog = true + } + // If we're unable to get log from "ctrld run", notice users about it. + if !haveLog { + mainLog.Load().Write([]byte(`"`)) + } } + // Report any error if occurred. + if err != nil { + _, _ = mainLog.Load().Write(marker) + msg := fmt.Sprintf("An error happened when performing test query: %s", err) + mainLog.Load().Write([]byte(msg)) + } + // If ctrld service is running but selfCheckStatus failed, it could be related + // to user's system firewall configuration, notice users about it. + if status == service.StatusRunning { + _, _ = mainLog.Load().Write(marker) + mainLog.Load().Write([]byte(`ctrld service was running, but somehow DNS query could not be sent to its listener`)) + mainLog.Load().Write([]byte(`Please check your system firewall if it is configured to block/intercept/redirect DNS queries`)) + } + _, _ = mainLog.Load().Write(marker) uninstall(p, s) os.Exit(1) @@ -1346,41 +1370,44 @@ func defaultIfaceName() string { return dri } -func selfCheckStatus(s service.Service) service.Status { +// selfCheckStatus performs the end-to-end DNS test by sending query to ctrld listener. +// It returns a boolean to indicate whether the check is succeeded, the actual status +// of ctrld service, and an additional error if any. +func selfCheckStatus(s service.Service) (bool, service.Status, error) { status, err := s.Status() if err != nil { mainLog.Load().Warn().Err(err).Msg("could not get service status") - return status + return false, service.StatusUnknown, err } // If ctrld is not running, do nothing, just return the status as-is. if status != service.StatusRunning { - return status + return false, status, nil } dir, err := socketDir() if err != nil { mainLog.Load().Error().Err(err).Msg("failed to check ctrld listener status: could not get home directory") - return service.StatusUnknown + return false, status, err } mainLog.Load().Debug().Msg("waiting for ctrld listener to be ready") cc := newSocketControlClient(s, dir) if cc == nil { - return service.StatusUnknown + return false, status, errors.New("could not connect to control server") } resp, err := cc.post(startedPath, nil) if err != nil { mainLog.Load().Error().Err(err).Msg("failed to connect to control server") - return service.StatusUnknown + return false, status, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { mainLog.Load().Error().Msg("ctrld listener is not ready") - return service.StatusUnknown + return false, status, errors.New("ctrld listener is not ready") } // Not a ctrld upstream, return status as-is. if cfg.FirstUpstream().VerifyDomain() == "" { - return status + return true, status, nil } mainLog.Load().Debug().Msg("ctrld listener is ready") @@ -1405,12 +1432,12 @@ func selfCheckStatus(s service.Service) service.Status { domain := cfg.FirstUpstream().VerifyDomain() if domain == "" { // Nothing to do, return the status as-is. - return status + return true, status, nil } watcher, err := fsnotify.NewWatcher() if err != nil { mainLog.Load().Error().Err(err).Msg("could not watch config change") - return service.StatusUnknown + return false, status, err } defer watcher.Close() @@ -1449,14 +1476,18 @@ func selfCheckStatus(s service.Service) service.Status { m := new(dns.Msg) m.SetQuestion(domain+".", dns.TypeA) m.RecursionDesired = true - r, _, err := c.ExchangeContext(ctx, m, net.JoinHostPort(lc.IP, strconv.Itoa(lc.Port))) + r, _, exErr := exchangeContextWithTimeout(c, time.Second, m, net.JoinHostPort(lc.IP, strconv.Itoa(lc.Port))) if r != nil && r.Rcode == dns.RcodeSuccess && len(r.Answer) > 0 { mainLog.Load().Debug().Msgf("self-check against %q succeeded", domain) - return status + return true, status, nil + } + // Return early if this is a connection refused. + if errConnectionRefused(exErr) { + return false, status, exErr } lastAnswer = r - lastErr = err - bo.BackOff(ctx, fmt.Errorf("ExchangeContext: %w", err)) + lastErr = exErr + bo.BackOff(ctx, fmt.Errorf("ExchangeContext: %w", exErr)) } mainLog.Load().Debug().Msgf("self-check against %q failed", domain) lc := cfg.FirstListener() @@ -1471,9 +1502,9 @@ func selfCheckStatus(s service.Service) service.Status { for _, s := range strings.Split(lastAnswer.String(), "\n") { mainLog.Load().Debug().Msgf("%s", s) } - mainLog.Load().Debug().Msg(marker) + return false, status, errSelfCheckNoAnswer } - return service.StatusUnknown + return false, status, lastErr } func userHomeDir() (string, error) { @@ -2130,3 +2161,10 @@ func ensureUninstall(s service.Service) error { } return errors.Join(err, errors.New("uninstall failed")) } + +// exchangeContextWithTimeout wraps c.ExchangeContext with the given timeout. +func exchangeContextWithTimeout(c *dns.Client, timeout time.Duration, msg *dns.Msg, addr string) (*dns.Msg, time.Duration, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return c.ExchangeContext(ctx, msg, addr) +} diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 8d0cf3e..92eadc8 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -586,6 +586,15 @@ func errNetworkError(err error) bool { return false } +// errConnectionRefused reports whether err is connection refused. +func errConnectionRefused(err error) bool { + var opErr *net.OpError + if !errors.As(err, &opErr) { + return false + } + return errors.Is(opErr.Err, syscall.ECONNREFUSED) || errors.Is(opErr.Err, windowsECONNREFUSED) +} + func ifaceFirstPrivateIP(iface *net.Interface) string { if iface == nil { return "" From 891b7cb2c6f572201c05ac59f53bc1cbb029513d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 7 Feb 2024 13:01:21 +0700 Subject: [PATCH 11/27] cmd/cli: integrating with Windows Server DNS feature Windows Server which is running Active Directory will have its own DNS server running. For typical setup, this DNS server will listen on all interfaces, and receiving queries from others to be able to resolve computer name in domain. That would make ctrld default setup never works, since ctrld can listen on port 53, but requests are never be routed to its listeners. To integrate ctrld in this case, we need to listen on a local IP address, then configure this IP as a Forwarder of local DNS server. With this setup, computer name on domain can still be resolved, and other queries can still be resolved by ctrld upstream as usual. --- cmd/cli/cli.go | 53 ++++++++++++++++++++++++++++++++++-- cmd/cli/os_windows.go | 63 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 0aa22df..9b6af8a 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -304,14 +304,14 @@ func initCLI() { // Report any error if occurred. if err != nil { _, _ = mainLog.Load().Write(marker) - msg := fmt.Sprintf("An error happened when performing test query: %s", err) + msg := fmt.Sprintf("An error occurred while performing test query: %s", err) mainLog.Load().Write([]byte(msg)) } // If ctrld service is running but selfCheckStatus failed, it could be related // to user's system firewall configuration, notice users about it. if status == service.StatusRunning { _, _ = mainLog.Load().Write(marker) - mainLog.Load().Write([]byte(`ctrld service was running, but somehow DNS query could not be sent to its listener`)) + mainLog.Load().Write([]byte(`ctrld service was running, but a DNS query could not be sent to its listener`)) mainLog.Load().Write([]byte(`Please check your system firewall if it is configured to block/intercept/redirect DNS queries`)) } @@ -1731,10 +1731,17 @@ func tryUpdateListenerConfig(cfg *ctrld.Config, infoLogger *zerolog.Logger, fata lcc := make(map[string]*listenerConfigCheck) cdMode := cdUID != "" nextdnsMode := nextdns != "" + // For Windows server with local Dns server running, we can only try on random local IP. + hasLocalDnsServer := windowsHasLocalDnsServerRunning() for n, listener := range cfg.Listener { lcc[n] = &listenerConfigCheck{} if listener.IP == "" { listener.IP = "0.0.0.0" + if hasLocalDnsServer { + // Windows Server lies to us that we could listen on 0.0.0.0:53 + // even there's a process already done that, stick to local IP only. + listener.IP = "127.0.0.1" + } lcc[n].IP = true } if listener.Port == 0 { @@ -1743,9 +1750,15 @@ func tryUpdateListenerConfig(cfg *ctrld.Config, infoLogger *zerolog.Logger, fata } // In cd mode, we always try to pick an ip:port pair to work. // Same if nextdns resolver is used. + // + // Except on Windows Server with local Dns running, + // we could only listen on random local IP port 53. if cdMode || nextdnsMode { lcc[n].IP = true lcc[n].Port = true + if hasLocalDnsServer { + lcc[n].Port = false + } } updated = updated || lcc[n].IP || lcc[n].Port } @@ -1831,6 +1844,11 @@ func tryUpdateListenerConfig(cfg *ctrld.Config, infoLogger *zerolog.Logger, fata tryAllPort53 := true tryOldIPPort5354 := true tryPort5354 := true + if hasLocalDnsServer { + tryAllPort53 = false + tryOldIPPort5354 = false + tryPort5354 = false + } attempts := 0 maxAttempts := 10 for { @@ -2168,3 +2186,34 @@ func exchangeContextWithTimeout(c *dns.Client, timeout time.Duration, msg *dns.M defer cancel() return c.ExchangeContext(ctx, msg, addr) } + +// powershell runs the given powershell command. +func powershell(cmd string) ([]byte, error) { + return exec.Command("powershell", "-Command", cmd).CombinedOutput() +} + +// windowsHasLocalDnsServerRunning reports whether we are on Windows and having Dns server running. +func windowsHasLocalDnsServerRunning() bool { + if runtime.GOOS == "windows" { + out, _ := powershell("Get-WindowsFeature -Name DNS") + if !bytes.Contains(bytes.ToLower(out), []byte("installed")) { + return false + } + + _, err := powershell("Get-Process -Name DNS") + return err == nil + } + return false +} + +// absHomeDir returns the absolute path to given filename using home directory as root dir. +func absHomeDir(filename string) string { + if homedir != "" { + return filepath.Join(homedir, filename) + } + dir, err := userHomeDir() + if err != nil { + return filename + } + return filepath.Join(dir, filename) +} diff --git a/cmd/cli/os_windows.go b/cmd/cli/os_windows.go index a58411e..694643f 100644 --- a/cmd/cli/os_windows.go +++ b/cmd/cli/os_windows.go @@ -2,19 +2,43 @@ package cli import ( "errors" + "fmt" "net" + "os" "os/exec" "strconv" + "strings" + "sync" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" ) +const forwardersFilename = ".forwarders.txt" + +var ( + setDNSOnce sync.Once + resetDNSOnce sync.Once +) + func setDNS(iface *net.Interface, nameservers []string) error { if len(nameservers) == 0 { return errors.New("empty DNS nameservers") } + setDNSOnce.Do(func() { + // If there's a Dns server running, that means we are on AD with Dns feature enabled. + // Configuring the Dns server to forward queries to ctrld instead. + if windowsHasLocalDnsServerRunning() { + file := absHomeDir(forwardersFilename) + if err := os.WriteFile(file, []byte(strings.Join(nameservers, ",")), 0600); err != nil { + mainLog.Load().Warn().Err(err).Msg("could not save forwarders settings") + } + if err := addDnsServerForwarders(nameservers); err != nil { + mainLog.Load().Warn().Err(err).Msg("could not set forwarders settings") + } + } + }) primaryDNS := nameservers[0] if err := setPrimaryDNS(iface, primaryDNS); err != nil { return err @@ -28,6 +52,23 @@ func setDNS(iface *net.Interface, nameservers []string) error { // TODO(cuonglm): should we use system API? func resetDNS(iface *net.Interface) error { + resetDNSOnce.Do(func() { + // See corresponding comment in setDNS. + if windowsHasLocalDnsServerRunning() { + file := absHomeDir(forwardersFilename) + content, err := os.ReadFile(file) + if err != nil { + mainLog.Load().Error().Err(err).Msg("could not read forwarders settings") + return + } + nameservers := strings.Split(string(content), ",") + if err := removeDnsServerForwarders(nameservers); err != nil { + mainLog.Load().Error().Err(err).Msg("could not remove forwarders settings") + return + } + } + }) + if ctrldnet.SupportsIPv6ListenLocal() { if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil { mainLog.Load().Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output)) @@ -93,3 +134,25 @@ func currentDNS(iface *net.Interface) []string { } return ns } + +// addDnsServerForwarders adds given nameservers to DNS server forwarders list. +func addDnsServerForwarders(nameservers []string) error { + for _, ns := range nameservers { + cmd := fmt.Sprintf("Add-DnsServerForwarder -IPAddress %s", ns) + if out, err := powershell(cmd); err != nil { + return fmt.Errorf("%w: %s", err, string(out)) + } + } + return nil +} + +// removeDnsServerForwarders removes given nameservers from DNS server forwarders list. +func removeDnsServerForwarders(nameservers []string) error { + for _, ns := range nameservers { + cmd := fmt.Sprintf("Remove-DnsServerForwarder -IPAddress %s -Force", ns) + if out, err := powershell(cmd); err != nil { + return fmt.Errorf("%w: %s", err, string(out)) + } + } + return nil +} From a163be35844b22f2b842a3d8736c83e86a65dfdb Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 7 Feb 2024 14:29:20 +0700 Subject: [PATCH 12/27] cmd/cli: preserve static DNS on Windows/Mac --- cmd/cli/os_darwin.go | 5 +++++ cmd/cli/os_windows.go | 5 +++++ cmd/cli/prog.go | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/cmd/cli/os_darwin.go b/cmd/cli/os_darwin.go index 5931819..1c61efd 100644 --- a/cmd/cli/os_darwin.go +++ b/cmd/cli/os_darwin.go @@ -44,6 +44,11 @@ func setDNS(iface *net.Interface, nameservers []string) error { // TODO(cuonglm): use system API func resetDNS(iface *net.Interface) error { + if ns := savedNameservers(iface); len(ns) > 0 { + if err := setDNS(iface, ns); err == nil { + return nil + } + } cmd := "networksetup" args := []string{"-setdnsservers", iface.Name, "empty"} diff --git a/cmd/cli/os_windows.go b/cmd/cli/os_windows.go index 694643f..f075ded 100644 --- a/cmd/cli/os_windows.go +++ b/cmd/cli/os_windows.go @@ -69,6 +69,11 @@ func resetDNS(iface *net.Interface) error { } }) + if ns := savedNameservers(iface); len(ns) > 0 { + if err := setDNS(iface, ns); err == nil { + return nil + } + } if ctrldnet.SupportsIPv6ListenLocal() { if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil { mainLog.Load().Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output)) diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 92eadc8..e25dcfb 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -462,6 +462,7 @@ func (p *prog) setDNS() { if needRFC1918Listeners(lc) { nameservers = append(nameservers, ctrld.Rfc1918Addresses()...) } + saveCurrentDNS(netIface) if err := setDNS(netIface, nameservers); err != nil { logger.Error().Err(err).Msgf("could not set DNS for interface") return @@ -469,6 +470,7 @@ func (p *prog) setDNS() { logger.Debug().Msg("setting DNS successfully") if allIfaces { withEachPhysicalInterfaces(netIface.Name, "set DNS", func(i *net.Interface) error { + saveCurrentDNS(i) return setDNS(i, nameservers) }) } @@ -724,3 +726,35 @@ func requiredMultiNICsConfig() bool { return false } } + +// saveCurrentDNS saves the current DNS settings for restoring later. +// Only works on Windows and Mac. +func saveCurrentDNS(iface *net.Interface) { + switch runtime.GOOS { + case "windows", "darwin": + default: + return + } + ns := currentDNS(iface) + if len(ns) == 0 { + return + } + file := savedDnsSettingsFilePath(iface) + if err := os.WriteFile(file, []byte(strings.Join(ns, ",")), 0600); err != nil { + mainLog.Load().Err(err).Msgf("could not save DNS settings for iface: %s", iface.Name) + } +} + +// savedDnsSettingsFilePath returns the path to saved DNS settings of the given interface. +func savedDnsSettingsFilePath(iface *net.Interface) string { + return absHomeDir(".dns_" + iface.Name) +} + +// savedNameservers returns the static DNS nameservers of the given interface. +func savedNameservers(iface *net.Interface) []string { + file := savedDnsSettingsFilePath(iface) + if data, err := os.ReadFile(file); err != nil && len(data) > 0 { + return strings.Split(string(data), ",") + } + return nil +} From bb481d9bcc0108e07828683ad410fcbf314dfb10 Mon Sep 17 00:00:00 2001 From: Ginder Singh Date: Wed, 7 Feb 2024 17:33:56 -0500 Subject: [PATCH 13/27] Added build script for mobile lib. --- scripts/build_lib.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100755 scripts/build_lib.sh diff --git a/scripts/build_lib.sh b/scripts/build_lib.sh new file mode 100755 index 0000000..332a5ef --- /dev/null +++ b/scripts/build_lib.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# This script is used to locally build Android .aar library and iOS .xcframework from ctrld source using go mobile tool. + +# Requirements: +# - Android NDK (version 23+) +# - Android SDK (version 33+) +# - Xcode 15 + Build tools +# - Go 1.21 +# - Git +# usage: $ ./build_lib.sh v1.3.4 + +TAG="$1" +# Hacky way to replace version info. +update_versionInfo() { + local file="$1/ctrld/cmd/cli/cli.go" + local tag="$2" + local commit="$3" + awk -v tag="$tag" -v commit="$commit" ' + BEGIN { version_updated = 0; commit_updated = 0 } + /^\tversion/ { + sub(/= ".+"/, "= \"" tag "\""); + version_updated = 1; + } + /^\tcommit/ { + sub(/= ".+"/, "= \"" commit "\""); + commit_updated = 1; + } + { print } + END { + if (version_updated == 0) { + print "\tversion = \"" tag "\""; + } + if (commit_updated == 0) { + print "\tcommit = \"" commit "\""; + } + } + ' "$file" > "$file.tmp" && mv "$file.tmp" "$file" +} +export ANDROID_NDK_HOME=~/Library/Android/sdk/ndk/23.0.7599858 +mkdir bin +cd bin || exit +root=$(pwd) +# Get source from github and switch to tag +git clone --depth 1 --branch "$TAG" https://github.com/Control-D-Inc/ctrld.git +# Prepare gomobile tool +sourcePath=./ctrld/cmd/ctrld_library +cd $sourcePath || exit +go mod tidy +go install golang.org/x/mobile/cmd/gomobile@latest +go get golang.org/x/mobile/bind +gomobile init +# Prepare build info +buildDir=$root/../build +mkdir -p "$buildDir" +COMMIT=$(git rev-parse HEAD) +update_versionInfo "$root" "$TAG" "$COMMIT" +ldflags="-s -w" +# Build +gomobile bind -target ios/arm64 -ldflags="$ldflags" -o "$buildDir"/ctrld-"$TAG".xcframework || exit +gomobile bind -ldflags="$ldflags" -o "$buildDir"/ctrld-"$TAG".aar || exit +# Clean up +rm -r "$root" +echo "Successfully built Ctrld library $TAG($COMMIT)." \ No newline at end of file From d55563cac5de565ad28e5de0bbab395ae844f4bd Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 8 Feb 2024 11:20:14 +0700 Subject: [PATCH 14/27] cmd/cli: removing current forwarders during setting DNS Otherwise, old staled forwarders will be set in Windows DNS each time the OS restart. --- cmd/cli/os_windows.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/cli/os_windows.go b/cmd/cli/os_windows.go index f075ded..4bcbc1b 100644 --- a/cmd/cli/os_windows.go +++ b/cmd/cli/os_windows.go @@ -31,6 +31,13 @@ func setDNS(iface *net.Interface, nameservers []string) error { // Configuring the Dns server to forward queries to ctrld instead. if windowsHasLocalDnsServerRunning() { file := absHomeDir(forwardersFilename) + if data, _ := os.ReadFile(file); len(data) > 0 { + if err := removeDnsServerForwarders(strings.Split(string(data), ",")); err != nil { + mainLog.Load().Error().Err(err).Msg("could not remove current forwarders settings") + } else { + mainLog.Load().Debug().Msg("removed current forwarders settings.") + } + } if err := os.WriteFile(file, []byte(strings.Join(nameservers, ",")), 0600); err != nil { mainLog.Load().Warn().Err(err).Msg("could not save forwarders settings") } From 18e861683450316bcbd5af05a78385f06dc8fde2 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 8 Feb 2024 08:59:53 +0700 Subject: [PATCH 15/27] cmd/cli: save DNS settings only once While at it, also fixing a bug in getting saved nameservers. --- cmd/cli/cli.go | 8 ++++++++ cmd/cli/prog.go | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 9b6af8a..de2e93c 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -268,6 +268,14 @@ func initCLI() { {s.Stop, false}, {func() error { return doGenerateNextDNSConfig(nextdns) }, true}, {func() error { return ensureUninstall(s) }, false}, + {func() error { + // Save current DNS so we can restore later. + withEachPhysicalInterfaces("", "save DNS settings", func(i *net.Interface) error { + saveCurrentDNS(i) + return nil + }) + return nil + }, false}, {s.Install, false}, {s.Start, true}, // Note that startCmd do not actually write ControlD config, but the config file was diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index e25dcfb..2f2bd0b 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -462,7 +462,6 @@ func (p *prog) setDNS() { if needRFC1918Listeners(lc) { nameservers = append(nameservers, ctrld.Rfc1918Addresses()...) } - saveCurrentDNS(netIface) if err := setDNS(netIface, nameservers); err != nil { logger.Error().Err(err).Msgf("could not set DNS for interface") return @@ -470,7 +469,6 @@ func (p *prog) setDNS() { logger.Debug().Msg("setting DNS successfully") if allIfaces { withEachPhysicalInterfaces(netIface.Name, "set DNS", func(i *net.Interface) error { - saveCurrentDNS(i) return setDNS(i, nameservers) }) } @@ -753,7 +751,7 @@ func savedDnsSettingsFilePath(iface *net.Interface) string { // savedNameservers returns the static DNS nameservers of the given interface. func savedNameservers(iface *net.Interface) []string { file := savedDnsSettingsFilePath(iface) - if data, err := os.ReadFile(file); err != nil && len(data) > 0 { + if data, _ := os.ReadFile(file); len(data) > 0 { return strings.Split(string(data), ",") } return nil From 4d810261a4bb26322e0ec84e071a05670bc9527a Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 9 Feb 2024 12:35:43 +0700 Subject: [PATCH 16/27] cmd/cli: only save/restore static DNS The save/restore DNS functionality always perform its job, even though the DNS is not static, aka set by DHCP. That may lead to confusion to users. Since DHCP settings was changed to static settings, even though the namesers set are the same. To fix this, ctrld should save/restore only there's actual static DNS set. For DHCP, thing should work as-is like we are doing. --- cmd/cli/cli.go | 8 ++--- cmd/cli/os_darwin.go | 24 +++++++++++++- cmd/cli/os_freebsd.go | 5 +++ cmd/cli/os_linux.go | 5 +++ cmd/cli/os_windows.go | 76 ++++++++++++++++++++++++++++++++++++++----- cmd/cli/prog.go | 30 ++++++++++------- 6 files changed, 122 insertions(+), 26 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index de2e93c..e97b53c 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -270,10 +270,7 @@ func initCLI() { {func() error { return ensureUninstall(s) }, false}, {func() error { // Save current DNS so we can restore later. - withEachPhysicalInterfaces("", "save DNS settings", func(i *net.Interface) error { - saveCurrentDNS(i) - return nil - }) + withEachPhysicalInterfaces("", "save DNS settings", saveCurrentStaticDNS) return nil }, false}, {s.Install, false}, @@ -2197,7 +2194,8 @@ func exchangeContextWithTimeout(c *dns.Client, timeout time.Duration, msg *dns.M // powershell runs the given powershell command. func powershell(cmd string) ([]byte, error) { - return exec.Command("powershell", "-Command", cmd).CombinedOutput() + out, err := exec.Command("powershell", "-Command", cmd).CombinedOutput() + return bytes.TrimSpace(out), err } // windowsHasLocalDnsServerRunning reports whether we are on Windows and having Dns server running. diff --git a/cmd/cli/os_darwin.go b/cmd/cli/os_darwin.go index 1c61efd..aa39094 100644 --- a/cmd/cli/os_darwin.go +++ b/cmd/cli/os_darwin.go @@ -1,6 +1,8 @@ package cli import ( + "bufio" + "bytes" "net" "os/exec" @@ -44,7 +46,7 @@ func setDNS(iface *net.Interface, nameservers []string) error { // TODO(cuonglm): use system API func resetDNS(iface *net.Interface) error { - if ns := savedNameservers(iface); len(ns) > 0 { + if ns := savedStaticNameservers(iface); len(ns) > 0 { if err := setDNS(iface, ns); err == nil { return nil } @@ -62,3 +64,23 @@ func resetDNS(iface *net.Interface) error { func currentDNS(_ *net.Interface) []string { return resolvconffile.NameServers("") } + +// currentStaticDNS returns the current static DNS settings of given interface. +func currentStaticDNS(iface *net.Interface) []string { + cmd := "networksetup" + args := []string{"-getdnsservers", iface.Name} + out, err := exec.Command(cmd, args...).Output() + if err != nil { + mainLog.Load().Error().Err(err).Msg("could not get current static DNS") + return nil + } + scanner := bufio.NewScanner(bytes.NewReader(out)) + var ns []string + for scanner.Scan() { + line := scanner.Text() + if ip := net.ParseIP(line); ip != nil { + ns = append(ns, ip.String()) + } + } + return ns +} diff --git a/cmd/cli/os_freebsd.go b/cmd/cli/os_freebsd.go index a6d6dde..a8de0c6 100644 --- a/cmd/cli/os_freebsd.go +++ b/cmd/cli/os_freebsd.go @@ -66,3 +66,8 @@ func resetDNS(iface *net.Interface) error { func currentDNS(_ *net.Interface) []string { return resolvconffile.NameServers("") } + +// currentStaticDNS returns the current static DNS settings of given interface. +func currentStaticDNS(iface *net.Interface) []string { + return currentDNS(iface) +} diff --git a/cmd/cli/os_linux.go b/cmd/cli/os_linux.go index 3036d03..c7661f0 100644 --- a/cmd/cli/os_linux.go +++ b/cmd/cli/os_linux.go @@ -203,6 +203,11 @@ func currentDNS(iface *net.Interface) []string { return nil } +// currentStaticDNS returns the current static DNS settings of given interface. +func currentStaticDNS(iface *net.Interface) []string { + return currentDNS(iface) +} + func getDNSByResolvectl(iface string) []string { b, err := exec.Command("resolvectl", "dns", "-i", iface).Output() if err != nil { diff --git a/cmd/cli/os_windows.go b/cmd/cli/os_windows.go index 4bcbc1b..bfb8cba 100644 --- a/cmd/cli/os_windows.go +++ b/cmd/cli/os_windows.go @@ -15,7 +15,11 @@ import ( ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" ) -const forwardersFilename = ".forwarders.txt" +const ( + forwardersFilename = ".forwarders.txt" + v4InterfaceKeyPathFormat = `HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\` + v6InterfaceKeyPathFormat = `HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\` +) var ( setDNSOnce sync.Once @@ -47,7 +51,7 @@ func setDNS(iface *net.Interface, nameservers []string) error { } }) primaryDNS := nameservers[0] - if err := setPrimaryDNS(iface, primaryDNS); err != nil { + if err := setPrimaryDNS(iface, primaryDNS, true); err != nil { return err } if len(nameservers) > 1 { @@ -76,25 +80,48 @@ func resetDNS(iface *net.Interface) error { } }) - if ns := savedNameservers(iface); len(ns) > 0 { - if err := setDNS(iface, ns); err == nil { - return nil - } - } + // Restoring ipv6 first. if ctrldnet.SupportsIPv6ListenLocal() { if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil { mainLog.Load().Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output)) } } + // Restoring ipv4 DHCP. output, err := netsh("interface", "ipv4", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp") if err != nil { mainLog.Load().Error().Err(err).Msgf("failed to reset ipv4 DNS: %s", string(output)) return err } + // If there's static DNS saved, restoring it. + if nss := savedStaticNameservers(iface); len(nss) > 0 { + v4ns := make([]string, 0, 2) + v6ns := make([]string, 0, 2) + for _, ns := range nss { + if ctrldnet.IsIPv6(ns) { + v6ns = append(v6ns, ns) + } else { + v4ns = append(v4ns, ns) + } + } + + for _, ns := range [][]string{v4ns, v6ns} { + if len(ns) == 0 { + continue + } + primaryDNS := ns[0] + if err := setPrimaryDNS(iface, primaryDNS, false); err != nil { + return err + } + if len(ns) > 1 { + secondaryDNS := ns[1] + _ = addSecondaryDNS(iface, secondaryDNS) + } + } + } return nil } -func setPrimaryDNS(iface *net.Interface, dns string) error { +func setPrimaryDNS(iface *net.Interface, dns string, disablev6 bool) error { ipVer := "ipv4" if ctrldnet.IsIPv6(dns) { ipVer = "ipv6" @@ -105,7 +132,7 @@ func setPrimaryDNS(iface *net.Interface, dns string) error { mainLog.Load().Error().Err(err).Msgf("failed to set primary DNS: %s", string(output)) return err } - if ipVer == "ipv4" && ctrldnet.SupportsIPv6ListenLocal() { + if disablev6 && ipVer == "ipv4" && ctrldnet.SupportsIPv6ListenLocal() { // Disable IPv6 DNS, so the query will be fallback to IPv4. _, _ = netsh("interface", "ipv6", "set", "dnsserver", idx, "static", "::1", "primary") } @@ -147,6 +174,37 @@ func currentDNS(iface *net.Interface) []string { return ns } +// currentStaticDNS returns the current static DNS settings of given interface. +func currentStaticDNS(iface *net.Interface) []string { + luid, err := winipcfg.LUIDFromIndex(uint32(iface.Index)) + if err != nil { + mainLog.Load().Error().Err(err).Msg("could not get interface LUID") + return nil + } + guid, err := luid.GUID() + if err != nil { + mainLog.Load().Error().Err(err).Msg("could not get interface GUID") + return nil + } + var ns []string + for _, path := range []string{v4InterfaceKeyPathFormat, v6InterfaceKeyPathFormat} { + interfaceKeyPath := path + guid.String() + found := false + for _, key := range []string{"NameServer", "ProfileNameServer"} { + if found { + continue + } + cmd := fmt.Sprintf(`Get-ItemPropertyValue -Path "%s" -Name "%s"`, interfaceKeyPath, key) + out, err := powershell(cmd) + if err == nil && len(out) > 0 { + found = true + ns = append(ns, strings.Split(string(out), ",")...) + } + } + } + return ns +} + // addDnsServerForwarders adds given nameservers to DNS server forwarders list. func addDnsServerForwarders(nameservers []string) error { for _, ns := range nameservers { diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 2f2bd0b..3042248 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -725,32 +725,40 @@ func requiredMultiNICsConfig() bool { } } -// saveCurrentDNS saves the current DNS settings for restoring later. +// saveCurrentStaticDNS saves the current static DNS settings for restoring later. // Only works on Windows and Mac. -func saveCurrentDNS(iface *net.Interface) { +func saveCurrentStaticDNS(iface *net.Interface) error { switch runtime.GOOS { case "windows", "darwin": default: - return + return nil } - ns := currentDNS(iface) + file := savedStaticDnsSettingsFilePath(iface) + if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) { + mainLog.Load().Warn().Err(err).Msg("could not remove old static DNS settings file") + } + ns := currentStaticDNS(iface) if len(ns) == 0 { - return + return nil } - file := savedDnsSettingsFilePath(iface) + mainLog.Load().Debug().Msgf("DNS settings for %s is static, saving ...", iface.Name) if err := os.WriteFile(file, []byte(strings.Join(ns, ",")), 0600); err != nil { mainLog.Load().Err(err).Msgf("could not save DNS settings for iface: %s", iface.Name) + return err } + return nil } -// savedDnsSettingsFilePath returns the path to saved DNS settings of the given interface. -func savedDnsSettingsFilePath(iface *net.Interface) string { +// savedStaticDnsSettingsFilePath returns the path to saved DNS settings of the given interface. +func savedStaticDnsSettingsFilePath(iface *net.Interface) string { return absHomeDir(".dns_" + iface.Name) } -// savedNameservers returns the static DNS nameservers of the given interface. -func savedNameservers(iface *net.Interface) []string { - file := savedDnsSettingsFilePath(iface) +// savedStaticNameservers returns the static DNS nameservers of the given interface. +// +//lint:ignore U1000 use in os_windows.go and os_darwin.go +func savedStaticNameservers(iface *net.Interface) []string { + file := savedStaticDnsSettingsFilePath(iface) if data, _ := os.ReadFile(file); len(data) > 0 { return strings.Split(string(data), ",") } From 5145729ab16ecd61cdb22d1bf56d448aaf3a80e6 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 10 Feb 2024 13:08:50 +0700 Subject: [PATCH 17/27] cmd/cli: always set/reset DNS regardless of interfaces state The interface may be down during ctrld uninstall, so the previous set DNS won't be restored, causing bad state when interface is up again. --- cmd/cli/prog.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 3042248..7286acd 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -682,12 +682,8 @@ func canBeLocalUpstream(addr string) bool { // log message when error happens. func withEachPhysicalInterfaces(excludeIfaceName, context string, f func(i *net.Interface) error) { interfaces.ForeachInterface(func(i interfaces.Interface, prefixes []netip.Prefix) { - // Skip not-running/local/virtual interface. - if !i.IsUp() || i.IsLoopback() || len(i.HardwareAddr) == 0 { - return - } - // Skip non-configured interfaces. - if addrs, _ := i.Addrs(); len(addrs) == 0 { + // Skip loopback/virtual interface. + if i.IsLoopback() || len(i.HardwareAddr) == 0 { return } // Skip invalid interface. From fdb82f6ec395cc0dab8e74e1191f34ea944f9091 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 13 Feb 2024 18:11:53 +0700 Subject: [PATCH 18/27] cmd/cli: only emit error for running interfaces While at it, also ensure setDNS/resetDNS return a wrapped error on Darwin/Windows, so the caller can decide whether to print the error to users. --- cmd/cli/cli.go | 5 +++++ cmd/cli/os_darwin.go | 17 ++++++++--------- cmd/cli/os_windows.go | 11 +++++++---- cmd/cli/prog.go | 4 +++- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index e97b53c..a386646 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -2223,3 +2223,8 @@ func absHomeDir(filename string) string { } return filepath.Join(dir, filename) } + +// ifaceUp reports whether the net interface is up. +func ifaceUp(iface *net.Interface) bool { + return iface != nil && iface.Flags&net.FlagUp != 0 +} diff --git a/cmd/cli/os_darwin.go b/cmd/cli/os_darwin.go index aa39094..3b21844 100644 --- a/cmd/cli/os_darwin.go +++ b/cmd/cli/os_darwin.go @@ -3,6 +3,7 @@ package cli import ( "bufio" "bytes" + "fmt" "net" "os/exec" @@ -36,10 +37,8 @@ func setDNS(iface *net.Interface, nameservers []string) error { cmd := "networksetup" args := []string{"-setdnsservers", iface.Name} args = append(args, nameservers...) - - if err := exec.Command(cmd, args...).Run(); err != nil { - mainLog.Load().Error().Err(err).Msgf("setDNS failed, ips = %q", nameservers) - return err + if out, err := exec.Command(cmd, args...).CombinedOutput(); err != nil { + return fmt.Errorf("%v: %w", string(out), err) } return nil } @@ -53,10 +52,8 @@ func resetDNS(iface *net.Interface) error { } cmd := "networksetup" args := []string{"-setdnsservers", iface.Name, "empty"} - - if err := exec.Command(cmd, args...).Run(); err != nil { - mainLog.Load().Error().Err(err).Msgf("resetDNS failed") - return err + if out, err := exec.Command(cmd, args...).CombinedOutput(); err != nil { + return fmt.Errorf("%v: %w", string(out), err) } return nil } @@ -71,7 +68,9 @@ func currentStaticDNS(iface *net.Interface) []string { args := []string{"-getdnsservers", iface.Name} out, err := exec.Command(cmd, args...).Output() if err != nil { - mainLog.Load().Error().Err(err).Msg("could not get current static DNS") + if ifaceUp(iface) { + mainLog.Load().Error().Err(err).Msg("could not get current static DNS") + } return nil } scanner := bufio.NewScanner(bytes.NewReader(out)) diff --git a/cmd/cli/os_windows.go b/cmd/cli/os_windows.go index bfb8cba..e185dc0 100644 --- a/cmd/cli/os_windows.go +++ b/cmd/cli/os_windows.go @@ -89,8 +89,7 @@ func resetDNS(iface *net.Interface) error { // Restoring ipv4 DHCP. output, err := netsh("interface", "ipv4", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp") if err != nil { - mainLog.Load().Error().Err(err).Msgf("failed to reset ipv4 DNS: %s", string(output)) - return err + return fmt.Errorf("%s: %w", string(output), err) } // If there's static DNS saved, restoring it. if nss := savedStaticNameservers(iface); len(nss) > 0 { @@ -178,12 +177,16 @@ func currentDNS(iface *net.Interface) []string { func currentStaticDNS(iface *net.Interface) []string { luid, err := winipcfg.LUIDFromIndex(uint32(iface.Index)) if err != nil { - mainLog.Load().Error().Err(err).Msg("could not get interface LUID") + if ifaceUp(iface) { + mainLog.Load().Error().Err(err).Msg("could not get interface LUID") + } return nil } guid, err := luid.GUID() if err != nil { - mainLog.Load().Error().Err(err).Msg("could not get interface GUID") + if ifaceUp(iface) { + mainLog.Load().Error().Err(err).Msg("could not get interface GUID") + } return nil } var ns []string diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 7286acd..1940bd4 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -704,7 +704,9 @@ func withEachPhysicalInterfaces(excludeIfaceName, context string, f func(i *net. return } if err := f(netIface); err != nil { - mainLog.Load().Warn().Err(err).Msgf("failed to %s for interface: %q", context, i.Name) + if ifaceUp(netIface) { + mainLog.Load().Warn().Err(err).Msgf("failed to %s for interface: %q", context, i.Name) + } } else { mainLog.Load().Debug().Msgf("%s for interface %q successfully", context, i.Name) } From 583718f234d06d70a099b02952994286a2255757 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 14 Feb 2024 09:52:18 +0700 Subject: [PATCH 19/27] cmd/cli: silent un-necessary error for physical interfaces loop The loop is run after the main interface DNS was set, thus the error would make noise to users. This commit removes the noise, by making currentStaticDNS returns an additional error, so it's up to the caller to decive whether to emit the error or not. Further, the physical interface loop will now only log when the callback function runs successfully. Emitting the callback error can be done in the future, until we can figure out how to detect physical interfaces in Go portably. --- cmd/cli/cli.go | 5 ----- cmd/cli/os_darwin.go | 9 +++------ cmd/cli/os_freebsd.go | 4 ++-- cmd/cli/os_linux.go | 4 ++-- cmd/cli/os_windows.go | 14 ++++---------- cmd/cli/prog.go | 9 +++------ 6 files changed, 14 insertions(+), 31 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index a386646..e97b53c 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -2223,8 +2223,3 @@ func absHomeDir(filename string) string { } return filepath.Join(dir, filename) } - -// ifaceUp reports whether the net interface is up. -func ifaceUp(iface *net.Interface) bool { - return iface != nil && iface.Flags&net.FlagUp != 0 -} diff --git a/cmd/cli/os_darwin.go b/cmd/cli/os_darwin.go index 3b21844..7ce4aa1 100644 --- a/cmd/cli/os_darwin.go +++ b/cmd/cli/os_darwin.go @@ -63,15 +63,12 @@ func currentDNS(_ *net.Interface) []string { } // currentStaticDNS returns the current static DNS settings of given interface. -func currentStaticDNS(iface *net.Interface) []string { +func currentStaticDNS(iface *net.Interface) ([]string, error) { cmd := "networksetup" args := []string{"-getdnsservers", iface.Name} out, err := exec.Command(cmd, args...).Output() if err != nil { - if ifaceUp(iface) { - mainLog.Load().Error().Err(err).Msg("could not get current static DNS") - } - return nil + return nil, err } scanner := bufio.NewScanner(bytes.NewReader(out)) var ns []string @@ -81,5 +78,5 @@ func currentStaticDNS(iface *net.Interface) []string { ns = append(ns, ip.String()) } } - return ns + return ns, nil } diff --git a/cmd/cli/os_freebsd.go b/cmd/cli/os_freebsd.go index a8de0c6..216b36f 100644 --- a/cmd/cli/os_freebsd.go +++ b/cmd/cli/os_freebsd.go @@ -68,6 +68,6 @@ func currentDNS(_ *net.Interface) []string { } // currentStaticDNS returns the current static DNS settings of given interface. -func currentStaticDNS(iface *net.Interface) []string { - return currentDNS(iface) +func currentStaticDNS(iface *net.Interface) ([]string, error) { + return currentDNS(iface), nil } diff --git a/cmd/cli/os_linux.go b/cmd/cli/os_linux.go index c7661f0..fcff741 100644 --- a/cmd/cli/os_linux.go +++ b/cmd/cli/os_linux.go @@ -204,8 +204,8 @@ func currentDNS(iface *net.Interface) []string { } // currentStaticDNS returns the current static DNS settings of given interface. -func currentStaticDNS(iface *net.Interface) []string { - return currentDNS(iface) +func currentStaticDNS(iface *net.Interface) ([]string, error) { + return currentDNS(iface), nil } func getDNSByResolvectl(iface string) []string { diff --git a/cmd/cli/os_windows.go b/cmd/cli/os_windows.go index e185dc0..56097f8 100644 --- a/cmd/cli/os_windows.go +++ b/cmd/cli/os_windows.go @@ -174,20 +174,14 @@ func currentDNS(iface *net.Interface) []string { } // currentStaticDNS returns the current static DNS settings of given interface. -func currentStaticDNS(iface *net.Interface) []string { +func currentStaticDNS(iface *net.Interface) ([]string, error) { luid, err := winipcfg.LUIDFromIndex(uint32(iface.Index)) if err != nil { - if ifaceUp(iface) { - mainLog.Load().Error().Err(err).Msg("could not get interface LUID") - } - return nil + return nil, err } guid, err := luid.GUID() if err != nil { - if ifaceUp(iface) { - mainLog.Load().Error().Err(err).Msg("could not get interface GUID") - } - return nil + return nil, err } var ns []string for _, path := range []string{v4InterfaceKeyPathFormat, v6InterfaceKeyPathFormat} { @@ -205,7 +199,7 @@ func currentStaticDNS(iface *net.Interface) []string { } } } - return ns + return ns, nil } // addDnsServerForwarders adds given nameservers to DNS server forwarders list. diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 1940bd4..03f2105 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -703,11 +703,8 @@ func withEachPhysicalInterfaces(excludeIfaceName, context string, f func(i *net. if strings.Contains(netIface.Name, "vEthernet") { return } - if err := f(netIface); err != nil { - if ifaceUp(netIface) { - mainLog.Load().Warn().Err(err).Msgf("failed to %s for interface: %q", context, i.Name) - } - } else { + // TODO: investigate whether we should report this error? + if err := f(netIface); err == nil { mainLog.Load().Debug().Msgf("%s for interface %q successfully", context, i.Name) } }) @@ -735,7 +732,7 @@ func saveCurrentStaticDNS(iface *net.Interface) error { if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) { mainLog.Load().Warn().Err(err).Msg("could not remove old static DNS settings file") } - ns := currentStaticDNS(iface) + ns, _ := currentStaticDNS(iface) if len(ns) == 0 { return nil } From dabbf2037b60e3eff03aa6a5ec8c0a80b442c17c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 19 Feb 2024 15:26:10 +0700 Subject: [PATCH 20/27] cmd/cli: do not allow running start command if pin code set While at it, also emitting a better error message when pin code was set but users do not provide --pin flag. --- cmd/cli/cli.go | 33 +++++++++++++++++++++++++++------ cmd/cli/control_server.go | 9 +++++++-- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index e97b53c..e7253c2 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -259,6 +259,13 @@ func initCLI() { return } + // If pin code was set, do not allow running start command. + if status, _ := s.Status(); status == service.StatusRunning { + if err := checkDeactivationPin(s); isCheckDeactivationPinErr(err) { + os.Exit(deactivationPinInvalidExitCode) + } + } + if router.Name() != "" && iface != "" { mainLog.Load().Debug().Msg("cleaning up router before installing") _ = p.router.Cleanup() @@ -393,7 +400,7 @@ func initCLI() { return } initLogging() - if err := checkDeactivationPin(s); errors.Is(err, errInvalidDeactivationPin) { + if err := checkDeactivationPin(s); isCheckDeactivationPinErr(err) { os.Exit(deactivationPinInvalidExitCode) } if doTasks([]task{{s.Stop, true}}) { @@ -552,7 +559,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, if iface == "" { iface = "auto" } - if err := checkDeactivationPin(s); errors.Is(err, errInvalidDeactivationPin) { + if err := checkDeactivationPin(s); isCheckDeactivationPinErr(err) { os.Exit(deactivationPinInvalidExitCode) } uninstall(p, s) @@ -834,7 +841,7 @@ func RunMobile(appConfig *AppConfig, appCallback *AppCallback, stopCh chan struc // CheckDeactivationPin checks if deactivation pin is valid func CheckDeactivationPin(pin int64) int { deactivationPin = pin - if err := checkDeactivationPin(nil); errors.Is(err, errInvalidDeactivationPin) { + if err := checkDeactivationPin(nil); isCheckDeactivationPinErr(err) { return deactivationPinInvalidExitCode } return 0 @@ -2146,6 +2153,9 @@ const deactivationPinInvalidExitCode = 126 // errInvalidDeactivationPin indicates that the deactivation pin is invalid. var errInvalidDeactivationPin = errors.New("deactivation pin is invalid") +// errRequiredDeactivationPin indicates that the deactivation pin is required but not provided by users. +var errRequiredDeactivationPin = errors.New("deactivation pin is required to stop or uninstall the service") + // checkDeactivationPin validates if the deactivation pin matches one in ControlD config. func checkDeactivationPin(s service.Service) error { dir, err := socketDir() @@ -2164,13 +2174,24 @@ func checkDeactivationPin(s service.Service) error { } data, _ := json.Marshal(&deactivationRequest{Pin: deactivationPin}) resp, _ := cc.post(deactivationPath, bytes.NewReader(data)) - if resp != nil && resp.StatusCode == http.StatusOK { - return nil // valid pin + if resp != nil { + switch resp.StatusCode { + case http.StatusBadRequest: + mainLog.Load().Error().Msg(errRequiredDeactivationPin.Error()) + return errRequiredDeactivationPin // pin is required + case http.StatusOK: + return nil // valid pin + } } - mainLog.Load().Error().Msg("deactivation pin is invalid") + mainLog.Load().Error().Msg(errInvalidDeactivationPin.Error()) return errInvalidDeactivationPin } +// isCheckDeactivationPinErr reports whether there is an error during check deactivation pin process. +func isCheckDeactivationPinErr(err error) bool { + return errors.Is(err, errInvalidDeactivationPin) || errors.Is(err, errRequiredDeactivationPin) +} + // ensureUninstall ensures that s.Uninstall will remove ctrld service from system completely. func ensureUninstall(s service.Service) error { maxAttempts := 10 diff --git a/cmd/cli/control_server.go b/cmd/cli/control_server.go index 117174d..28c20a6 100644 --- a/cmd/cli/control_server.go +++ b/cmd/cli/control_server.go @@ -160,9 +160,14 @@ func (p *prog) registerControlServerHandler() { mainLog.Load().Err(err).Msg("invalid deactivation request") return } - code := http.StatusBadRequest - if req.Pin == cdDeactivationPin { + + code := http.StatusForbidden + switch req.Pin { + case cdDeactivationPin: code = http.StatusOK + case defaultDeactivationPin: + // If the pin code was set, but users do not provide --pin, return proper code to client. + code = http.StatusBadRequest } w.WriteHeader(code) })) From 906479a15cc0eef0966c4a165f06fd966032654e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 20 Feb 2024 15:16:24 +0700 Subject: [PATCH 21/27] cmd/cli: do not save static DNS when ctrld is already installed If ctrld was installed, the DNS setting was changed, we could not determine the dynamic or static settings before installing ctrld. --- cmd/cli/cli.go | 17 +++++++++++++++-- cmd/cli/prog.go | 7 ++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index e7253c2..2009ab5 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -259,8 +259,11 @@ func initCLI() { return } + status, err := s.Status() + isCtrldInstalled := !errors.Is(err, service.ErrNotInstalled) + // If pin code was set, do not allow running start command. - if status, _ := s.Status(); status == service.StatusRunning { + if status == service.StatusRunning { if err := checkDeactivationPin(s); isCheckDeactivationPinErr(err) { os.Exit(deactivationPinInvalidExitCode) } @@ -276,8 +279,18 @@ func initCLI() { {func() error { return doGenerateNextDNSConfig(nextdns) }, true}, {func() error { return ensureUninstall(s) }, false}, {func() error { + // If ctrld is installed, we should not save current DNS settings, because: + // + // - The DNS settings was being set by ctrld already. + // - We could not determine the state of DNS settings before installing ctrld. + if isCtrldInstalled { + return nil + } + // Save current DNS so we can restore later. - withEachPhysicalInterfaces("", "save DNS settings", saveCurrentStaticDNS) + withEachPhysicalInterfaces("", "save DNS settings", func(i *net.Interface) error { + return saveCurrentStaticDNS(i) + }) return nil }, false}, {s.Install, false}, diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 03f2105..2b5accc 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "io/fs" "math/rand" "net" "net/netip" @@ -729,13 +730,13 @@ func saveCurrentStaticDNS(iface *net.Interface) error { return nil } file := savedStaticDnsSettingsFilePath(iface) - if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) { - mainLog.Load().Warn().Err(err).Msg("could not remove old static DNS settings file") - } ns, _ := currentStaticDNS(iface) if len(ns) == 0 { return nil } + if err := os.Remove(file); err != nil && !errors.Is(err, fs.ErrNotExist) { + mainLog.Load().Warn().Err(err).Msg("could not remove old static DNS settings file") + } mainLog.Load().Debug().Msgf("DNS settings for %s is static, saving ...", iface.Name) if err := os.WriteFile(file, []byte(strings.Join(ns, ",")), 0600); err != nil { mainLog.Load().Err(err).Msgf("could not save DNS settings for iface: %s", iface.Name) From 8f189c919a888bf08436c015233775242d2edf0e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 21 Feb 2024 18:00:25 +0700 Subject: [PATCH 22/27] cmd/cli: skip deactivation check for old socket server If the server is running old version of ctrld, the deactivation pin check will return 404 not found, the client should consider this as no error instead of returning invalid pin code. This allows v1.3.5 binary `ctrld start` command while the ctrld server is still running old version. I discover this while testing v1.3.5 binary on a router with old ctrld version running. --- cmd/cli/cli.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 2009ab5..6d6360f 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -2194,6 +2194,8 @@ func checkDeactivationPin(s service.Service) error { return errRequiredDeactivationPin // pin is required case http.StatusOK: return nil // valid pin + case http.StatusNotFound: + return nil // the server is running older version of ctrld } } mainLog.Load().Error().Msg(errInvalidDeactivationPin.Error()) From 7dc5138e91b4a6ea5f859f465db3bb5e54ad1f15 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 21 Feb 2024 15:48:40 +0700 Subject: [PATCH 23/27] cmd/cli: watch resolv.conf on all unix platforms --- cmd/cli/os_linux.go | 69 --------------------------- cmd/cli/prog.go | 7 +++ cmd/cli/resolvconf.go | 65 +++++++++++++++++++++++++ cmd/cli/resolvconf_darwin.go | 20 ++++++++ cmd/cli/resolvconf_not_darwin_unix.go | 40 ++++++++++++++++ cmd/cli/resolvconf_windows.go | 16 +++++++ 6 files changed, 148 insertions(+), 69 deletions(-) create mode 100644 cmd/cli/resolvconf.go create mode 100644 cmd/cli/resolvconf_darwin.go create mode 100644 cmd/cli/resolvconf_not_darwin_unix.go create mode 100644 cmd/cli/resolvconf_windows.go diff --git a/cmd/cli/os_linux.go b/cmd/cli/os_linux.go index fcff741..3d9bffd 100644 --- a/cmd/cli/os_linux.go +++ b/cmd/cli/os_linux.go @@ -9,12 +9,10 @@ import ( "net" "net/netip" "os/exec" - "path/filepath" "strings" "syscall" "time" - "github.com/fsnotify/fsnotify" "github.com/insomniacslk/dhcp/dhcpv4/nclient4" "github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/dhcpv6/client6" @@ -25,11 +23,6 @@ import ( "github.com/Control-D-Inc/ctrld/internal/resolvconffile" ) -const ( - resolvConfPath = "/etc/resolv.conf" - resolvConfBackupFailedMsg = "open /etc/resolv.pre-ctrld-backup.conf: read-only file system" -) - // allocate loopback ip // sudo ip a add 127.0.0.2/24 dev lo func allocateIP(ip string) error { @@ -69,12 +62,6 @@ func setDNS(iface *net.Interface, nameservers []string) error { Nameservers: ns, SearchDomains: []dnsname.FQDN{}, } - defer func() { - if r.Mode() == "direct" { - go watchResolveConf(osConfig) - } - }() - trySystemdResolve := false for i := 0; i < maxSetDNSAttempts; i++ { if err := r.SetDNS(osConfig); err != nil { @@ -314,59 +301,3 @@ func sliceIndex[S ~[]E, E comparable](s S, v E) int { } return -1 } - -// watchResolveConf watches any changes to /etc/resolv.conf file, -// and reverting to the original config set by ctrld. -func watchResolveConf(oc dns.OSConfig) { - mainLog.Load().Debug().Msg("start watching /etc/resolv.conf file") - watcher, err := fsnotify.NewWatcher() - if err != nil { - mainLog.Load().Warn().Err(err).Msg("could not create watcher for /etc/resolv.conf") - return - } - - // We watch /etc instead of /etc/resolv.conf directly, - // see: https://github.com/fsnotify/fsnotify#watching-a-file-doesnt-work-well - watchDir := filepath.Dir(resolvConfPath) - if err := watcher.Add(watchDir); err != nil { - mainLog.Load().Warn().Err(err).Msg("could not add /etc/resolv.conf to watcher list") - return - } - - r, err := dns.NewOSConfigurator(func(format string, args ...any) {}, "lo") // interface name does not matter. - if err != nil { - mainLog.Load().Error().Err(err).Msg("failed to create DNS OS configurator") - return - } - - for { - select { - case event, ok := <-watcher.Events: - if !ok { - return - } - if event.Name != resolvConfPath { // skip if not /etc/resolv.conf changes. - continue - } - if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) { - mainLog.Load().Debug().Msg("/etc/resolv.conf changes detected, reverting to ctrld setting") - if err := watcher.Remove(watchDir); err != nil { - mainLog.Load().Error().Err(err).Msg("failed to pause watcher") - continue - } - if err := r.SetDNS(oc); err != nil { - mainLog.Load().Error().Err(err).Msg("failed to revert /etc/resolv.conf changes") - } - if err := watcher.Add(watchDir); err != nil { - mainLog.Load().Error().Err(err).Msg("failed to continue running watcher") - return - } - } - case err, ok := <-watcher.Errors: - if !ok { - return - } - mainLog.Load().Err(err).Msg("could not get event for /etc/resolv.conf") - } - } -} diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 2b5accc..0716e9d 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -468,6 +468,13 @@ func (p *prog) setDNS() { return } logger.Debug().Msg("setting DNS successfully") + if shouldWatchResolvconf() { + servers := make([]netip.Addr, len(nameservers)) + for i := range nameservers { + servers[i] = netip.MustParseAddr(nameservers[i]) + } + go watchResolvConf(netIface, servers, setResolvConf) + } if allIfaces { withEachPhysicalInterfaces(netIface.Name, "set DNS", func(i *net.Interface) error { return setDNS(i, nameservers) diff --git a/cmd/cli/resolvconf.go b/cmd/cli/resolvconf.go new file mode 100644 index 0000000..f09d864 --- /dev/null +++ b/cmd/cli/resolvconf.go @@ -0,0 +1,65 @@ +package cli + +import ( + "net" + "net/netip" + "path/filepath" + + "github.com/fsnotify/fsnotify" +) + +const ( + resolvConfPath = "/etc/resolv.conf" + resolvConfBackupFailedMsg = "open /etc/resolv.pre-ctrld-backup.conf: read-only file system" +) + +// watchResolvConf watches any changes to /etc/resolv.conf file, +// and reverting to the original config set by ctrld. +func watchResolvConf(iface *net.Interface, ns []netip.Addr, setDnsFn func(iface *net.Interface, ns []netip.Addr) error) { + mainLog.Load().Debug().Msg("start watching /etc/resolv.conf file") + watcher, err := fsnotify.NewWatcher() + if err != nil { + mainLog.Load().Warn().Err(err).Msg("could not create watcher for /etc/resolv.conf") + return + } + defer watcher.Close() + + // We watch /etc instead of /etc/resolv.conf directly, + // see: https://github.com/fsnotify/fsnotify#watching-a-file-doesnt-work-well + watchDir := filepath.Dir(resolvConfPath) + if err := watcher.Add(watchDir); err != nil { + mainLog.Load().Warn().Err(err).Msg("could not add /etc/resolv.conf to watcher list") + return + } + + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Name != resolvConfPath { // skip if not /etc/resolv.conf changes. + continue + } + if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) { + mainLog.Load().Debug().Msg("/etc/resolv.conf changes detected, reverting to ctrld setting") + if err := watcher.Remove(watchDir); err != nil { + mainLog.Load().Error().Err(err).Msg("failed to pause watcher") + continue + } + if err := setDnsFn(iface, ns); err != nil { + mainLog.Load().Error().Err(err).Msg("failed to revert /etc/resolv.conf changes") + } + if err := watcher.Add(watchDir); err != nil { + mainLog.Load().Error().Err(err).Msg("failed to continue running watcher") + return + } + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + mainLog.Load().Err(err).Msg("could not get event for /etc/resolv.conf") + } + } +} diff --git a/cmd/cli/resolvconf_darwin.go b/cmd/cli/resolvconf_darwin.go new file mode 100644 index 0000000..7e26f41 --- /dev/null +++ b/cmd/cli/resolvconf_darwin.go @@ -0,0 +1,20 @@ +package cli + +import ( + "net" + "net/netip" +) + +// setResolvConf sets the content of resolv.conf file using the given nameservers list. +func setResolvConf(iface *net.Interface, ns []netip.Addr) error { + servers := make([]string, len(ns)) + for i := range ns { + servers[i] = ns[i].String() + } + return setDNS(iface, servers) +} + +// shouldWatchResolvconf reports whether ctrld should watch changes to resolv.conf file with given OS configurator. +func shouldWatchResolvconf() bool { + return true +} diff --git a/cmd/cli/resolvconf_not_darwin_unix.go b/cmd/cli/resolvconf_not_darwin_unix.go new file mode 100644 index 0000000..b98496e --- /dev/null +++ b/cmd/cli/resolvconf_not_darwin_unix.go @@ -0,0 +1,40 @@ +//go:build unix && !darwin + +package cli + +import ( + "net" + "net/netip" + + "tailscale.com/util/dnsname" + + "github.com/Control-D-Inc/ctrld/internal/dns" +) + +// setResolvConf sets the content of resolv.conf file using the given nameservers list. +func setResolvConf(iface *net.Interface, ns []netip.Addr) error { + r, err := dns.NewOSConfigurator(func(format string, args ...any) {}, "lo") // interface name does not matter. + if err != nil { + return err + } + + oc := dns.OSConfig{ + Nameservers: ns, + SearchDomains: []dnsname.FQDN{}, + } + return r.SetDNS(oc) +} + +// shouldWatchResolvconf reports whether ctrld should watch changes to resolv.conf file with given OS configurator. +func shouldWatchResolvconf() bool { + r, err := dns.NewOSConfigurator(func(format string, args ...any) {}, "lo") // interface name does not matter. + if err != nil { + return false + } + switch r.Mode() { + case "direct", "resolvconf": + return true + default: + return false + } +} diff --git a/cmd/cli/resolvconf_windows.go b/cmd/cli/resolvconf_windows.go new file mode 100644 index 0000000..3e4ba1c --- /dev/null +++ b/cmd/cli/resolvconf_windows.go @@ -0,0 +1,16 @@ +package cli + +import ( + "net" + "net/netip" +) + +// setResolvConf sets the content of resolv.conf file using the given nameservers list. +func setResolvConf(_ *net.Interface, _ []netip.Addr) error { + return nil +} + +// shouldWatchResolvconf reports whether ctrld should watch changes to resolv.conf file with given OS configurator. +func shouldWatchResolvconf() bool { + return false +} From 9319d080461838f42e37c49d1504875494896ccb Mon Sep 17 00:00:00 2001 From: Yegor Sak Date: Fri, 23 Feb 2024 21:42:24 +0000 Subject: [PATCH 24/27] Update file config.md --- docs/config.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/config.md b/docs/config.md index 404606d..d8fe701 100644 --- a/docs/config.md +++ b/docs/config.md @@ -224,11 +224,8 @@ DHCP leases file format. - Default: "" ### client_id_preference -Decide how the client ID is generated +Decide how the client ID is generated. By default client ID will use both MAC address and Hostname i.e. `hash(mac + host)`. To override this behavior, select one of the 2 allowed values to scope client ID to just MAC address OR Hostname. -If `host` -> client id will only use the hostname i.e.`hash(hostname)`. -If `mac` -> client id will only use the MAC address `hash(mac)`. -Else -> client ID will use both Mac and Hostname i.e. `hash(mac + host) - Type: string - Required: no - Valid values: `mac`, `host` From 73a697b2fa00f2495bd5ab38233702a45e247b7d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 27 Feb 2024 22:36:31 +0700 Subject: [PATCH 25/27] cmd/cli: remove old DNS settings on installing --- cmd/cli/prog.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 0716e9d..e7dd2ba 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -739,6 +739,7 @@ func saveCurrentStaticDNS(iface *net.Interface) error { file := savedStaticDnsSettingsFilePath(iface) ns, _ := currentStaticDNS(iface) if len(ns) == 0 { + _ = os.Remove(file) // removing old static DNS settings return nil } if err := os.Remove(file); err != nil && !errors.Is(err, fs.ErrNotExist) { From e89021ec3a15323e6e6c20504a05fcd2ab07901b Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 1 Mar 2024 15:16:40 +0700 Subject: [PATCH 26/27] cmd/cli: only set DNS for physical interfaces on Windows By filtering the interfaces by MAC address instead of name. --- cmd/cli/net.go | 34 ++++++++++++++++++++++++++++++++++ cmd/cli/net_others.go | 2 +- cmd/cli/net_windows.go | 21 +++++++++++++++++++++ cmd/cli/prog.go | 10 +++++----- 4 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 cmd/cli/net.go create mode 100644 cmd/cli/net_windows.go diff --git a/cmd/cli/net.go b/cmd/cli/net.go new file mode 100644 index 0000000..80da827 --- /dev/null +++ b/cmd/cli/net.go @@ -0,0 +1,34 @@ +package cli + +import "strings" + +// Copied from https://gist.github.com/Ultraporing/fe52981f678be6831f747c206a4861cb + +// Mac Address parts to look for, and identify non-physical devices. There may be more, update me! +var macAddrPartsToFilter = []string{ + "00:03:FF", // Microsoft Hyper-V, Virtual Server, Virtual PC + "0A:00:27", // VirtualBox + "00:00:00:00:00", // Teredo Tunneling Pseudo-Interface + "00:50:56", // VMware ESX 3, Server, Workstation, Player + "00:1C:14", // VMware ESX 3, Server, Workstation, Player + "00:0C:29", // VMware ESX 3, Server, Workstation, Player + "00:05:69", // VMware ESX 3, Server, Workstation, Player + "00:1C:42", // Microsoft Hyper-V, Virtual Server, Virtual PC + "00:0F:4B", // Virtual Iron 4 + "00:16:3E", // Red Hat Xen, Oracle VM, XenSource, Novell Xen + "08:00:27", // Sun xVM VirtualBox + "7A:79", // Hamachi +} + +// Filters the possible physical interface address by comparing it to known popular VM Software addresses +// and Teredo Tunneling Pseudo-Interface. +// +//lint:ignore U1000 use in net_windows.go +func isPhysicalInterface(addr string) bool { + for _, macPart := range macAddrPartsToFilter { + if strings.HasPrefix(strings.ToLower(addr), strings.ToLower(macPart)) { + return false + } + } + return true +} diff --git a/cmd/cli/net_others.go b/cmd/cli/net_others.go index c27a608..ebe7ba0 100644 --- a/cmd/cli/net_others.go +++ b/cmd/cli/net_others.go @@ -1,4 +1,4 @@ -//go:build !darwin +//go:build !darwin && !windows package cli diff --git a/cmd/cli/net_windows.go b/cmd/cli/net_windows.go new file mode 100644 index 0000000..c75ee32 --- /dev/null +++ b/cmd/cli/net_windows.go @@ -0,0 +1,21 @@ +package cli + +import ( + "net" +) + +func patchNetIfaceName(iface *net.Interface) error { + return nil +} + +// validInterface reports whether the *net.Interface is a valid one. +// On Windows, only physical interfaces are considered valid. +func validInterface(iface *net.Interface) bool { + if iface == nil { + return false + } + if isPhysicalInterface(iface.HardwareAddr.String()) { + return true + } + return false +} diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index e7dd2ba..6febff8 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -707,13 +707,11 @@ func withEachPhysicalInterfaces(excludeIfaceName, context string, f func(i *net. if netIface.Name == excludeIfaceName { return } - // Skip Windows Hyper-V Default Switch. - if strings.Contains(netIface.Name, "vEthernet") { - return - } // TODO: investigate whether we should report this error? if err := f(netIface); err == nil { mainLog.Load().Debug().Msgf("%s for interface %q successfully", context, i.Name) + } else if !errors.Is(err, errSaveCurrentStaticDNSNotSupported) { + mainLog.Load().Err(err).Msgf("%s for interface %q failed", context, i.Name) } }) } @@ -728,13 +726,15 @@ func requiredMultiNICsConfig() bool { } } +var errSaveCurrentStaticDNSNotSupported = errors.New("saving current DNS is not supported on this platform") + // saveCurrentStaticDNS saves the current static DNS settings for restoring later. // Only works on Windows and Mac. func saveCurrentStaticDNS(iface *net.Interface) error { switch runtime.GOOS { case "windows", "darwin": default: - return nil + return errSaveCurrentStaticDNSNotSupported } file := savedStaticDnsSettingsFilePath(iface) ns, _ := currentStaticDNS(iface) From 49eebcdcbc41b47ec1162c341cf7b07447029d10 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 7 Feb 2024 14:48:48 +0700 Subject: [PATCH 27/27] .github/workflows: bump go version to 1.21.x --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74f72a0..074d713 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: ["windows-latest", "ubuntu-latest", "macOS-latest"] - go: ["1.20.x"] + go: ["1.21.x"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3