From 34758f6205d2dbb34133ccbb40aa2eaafea6bd8c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 22 Sep 2023 11:19:22 +0000 Subject: [PATCH] Sending OS information in DoH header --- client_info.go | 1 + cmd/cli/dns_proxy.go | 29 ++++++++++++++++++++++ doh.go | 57 ++++++++++++++++++++++++++++++++++++++++++++ doh_test.go | 23 ++++++++++++++++++ go.mod | 2 +- go.sum | 2 ++ 6 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 doh_test.go diff --git a/client_info.go b/client_info.go index c4494f7..f32526a 100644 --- a/client_info.go +++ b/client_info.go @@ -8,6 +8,7 @@ type ClientInfo struct { Mac string IP string Hostname string + Self bool } // LeaseFileFormat specifies the format of DHCP lease file. diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index 445ae70..12cf781 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -16,6 +16,7 @@ import ( "github.com/miekg/dns" "golang.org/x/sync/errgroup" "tailscale.com/net/interfaces" + "tailscale.com/net/netaddr" "github.com/Control-D-Inc/ctrld" "github.com/Control-D-Inc/ctrld/internal/dnscache" @@ -510,6 +511,7 @@ func (p *prog) getClientInfo(remoteIP string, msg *dns.Msg) *ctrld.ClientInfo { ci.IP = p.appCallback.LanIp() ci.Mac = p.appCallback.MacAddress() ci.Hostname = p.appCallback.HostName() + ci.Self = true return ci } ci.IP, ci.Mac = ipAndMacFromMsg(msg) @@ -542,9 +544,36 @@ func (p *prog) getClientInfo(remoteIP string, msg *dns.Msg) *ctrld.ClientInfo { } else { ci.Hostname = p.ciTable.LookupHostname(ci.IP, ci.Mac) } + ci.Self = queryFromSelf(ci.IP) return ci } +// queryFromSelf reports whether the input IP is from device running ctrld. +func queryFromSelf(ip string) bool { + netIP := netip.MustParseAddr(ip) + ifaces, err := interfaces.GetList() + if err != nil { + mainLog.Load().Warn().Err(err).Msg("could not get interfaces list") + return false + } + for _, iface := range ifaces { + addrs, err := iface.Addrs() + if err != nil { + mainLog.Load().Warn().Err(err).Msgf("could not get interfaces addresses: %s", iface.Name) + continue + } + for _, a := range addrs { + switch v := a.(type) { + case *net.IPNet: + if pfx, ok := netaddr.FromStdIPNet(v); ok && pfx.Addr().Compare(netIP) == 0 { + return true + } + } + } + } + return false +} + func needRFC1918Listeners(lc *ctrld.ListenerConfig) bool { return lc.IP == "127.0.0.1" && lc.Port == 53 } diff --git a/doh.go b/doh.go index d0525d4..e0aa363 100644 --- a/doh.go +++ b/doh.go @@ -8,6 +8,11 @@ import ( "io" "net/http" "net/url" + "runtime" + "strings" + "sync" + + "github.com/cuonglm/osinfo" "github.com/miekg/dns" ) @@ -16,9 +21,56 @@ const ( dohMacHeader = "x-cd-mac" dohIPHeader = "x-cd-ip" dohHostHeader = "x-cd-host" + dohOsHeader = "x-cd-os" headerApplicationDNS = "application/dns-message" ) +// EncodeOsNameMap provides mapping from OS name to a shorter string, used for encoding x-cd-os value. +var EncodeOsNameMap = map[string]string{ + "windows": "1", + "darwin": "2", + "linux": "3", + "freebsd": "4", +} + +// DecodeOsNameMap provides mapping from encoded OS name to real value, used for decoding x-cd-os value. +var DecodeOsNameMap = map[string]string{} + +// EncodeArchNameMap provides mapping from OS arch to a shorter string, used for encoding x-cd-os value. +var EncodeArchNameMap = map[string]string{ + "amd64": "1", + "arm64": "2", + "arm": "3", + "386": "4", + "mips": "5", + "mipsle": "6", + "mips64": "7", +} + +// DecodeArchNameMap provides mapping from encoded OS arch to real value, used for decoding x-cd-os value. +var DecodeArchNameMap = map[string]string{} + +func init() { + for k, v := range EncodeOsNameMap { + DecodeOsNameMap[v] = k + } + for k, v := range EncodeArchNameMap { + DecodeArchNameMap[v] = k + } +} + +// TODO: use sync.OnceValue when upgrading to go1.21 +var xCdOsValueOnce sync.Once +var xCdOsValue string + +func dohOsHeaderValue() string { + xCdOsValueOnce.Do(func() { + oi := osinfo.New() + xCdOsValue = strings.Join([]string{EncodeOsNameMap[runtime.GOOS], EncodeArchNameMap[runtime.GOARCH], oi.Dist}, "-") + }) + return xCdOsValue +} + func newDohResolver(uc *UpstreamConfig) *dohResolver { r := &dohResolver{ endpoint: uc.u, @@ -97,6 +149,8 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) { req.Header.Set("Content-Type", headerApplicationDNS) req.Header.Set("Accept", headerApplicationDNS) + req.Header.Set(dohOsHeader, dohOsHeaderValue()) + printed := false if sendClientInfo { if ci, ok := ctx.Value(ClientInfoCtxKey{}).(*ClientInfo); ok && ci != nil { @@ -110,6 +164,9 @@ func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) { if ci.Hostname != "" { req.Header.Set(dohHostHeader, ci.Hostname) } + if ci.Self { + req.Header.Set(dohOsHeader, dohOsHeaderValue()) + } } } if printed { diff --git a/doh_test.go b/doh_test.go new file mode 100644 index 0000000..d233498 --- /dev/null +++ b/doh_test.go @@ -0,0 +1,23 @@ +package ctrld + +import ( + "runtime" + "testing" +) + +func Test_dohOsHeaderValue(t *testing.T) { + val := dohOsHeaderValue() + if val == "" { + t.Fatalf("empty %s", dohOsHeader) + } + t.Log(val) + + encodedOs := EncodeOsNameMap[runtime.GOOS] + if encodedOs == "" { + t.Fatalf("missing encoding value for: %q", runtime.GOOS) + } + decodedOs := DecodeOsNameMap[encodedOs] + if decodedOs == "" { + t.Fatalf("missing decoding value for: %q", runtime.GOOS) + } +} diff --git a/go.mod b/go.mod index 9f3e934..58ba1e4 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/coreos/go-systemd/v22 v22.5.0 - github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19 + github.com/cuonglm/osinfo v0.0.0-20230921071424-e0e1b1e0bbbf github.com/frankban/quicktest v1.14.5 github.com/fsnotify/fsnotify v1.6.0 github.com/go-playground/validator/v10 v10.11.1 diff --git a/go.sum b/go.sum index bd7a9af..409133a 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19 h1:7P/f19Mr0oa3ug8BYt4JuRe/Zq3dF4Mrr4m8+Kw+Hcs= github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19/go.mod h1:G45410zMgmnSjLVKCq4f6GpbYAzoP2plX9rPwgx6C24= +github.com/cuonglm/osinfo v0.0.0-20230921071424-e0e1b1e0bbbf h1:40DHYsri+d1bnroFDU2FQAeq68f3kAlOzlQ93kCf26Q= +github.com/cuonglm/osinfo v0.0.0-20230921071424-e0e1b1e0bbbf/go.mod h1:G45410zMgmnSjLVKCq4f6GpbYAzoP2plX9rPwgx6C24= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=