From 41846b6d4c79ac6a7b594d2a2076026cf85906a8 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 12 Dec 2023 18:34:41 +0700 Subject: [PATCH] all: add config to enable/disable answering WAN clients --- cmd/cli/dns_proxy.go | 27 ++++++++++++++++++++++++--- cmd/cli/dns_proxy_test.go | 26 ++++++++++++++++++++++++++ config.go | 9 +++++---- docs/config.md | 9 ++++++++- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index 0a68071..2b0f94d 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -77,12 +77,21 @@ func (p *prog) serveDNS(listenerNum string) error { if len(m.Question) == 0 { answer := new(dns.Msg) answer.SetRcode(m, dns.RcodeFormatError) + _ = w.WriteMsg(answer) + return + } + reqId := requestID() + ctx := context.WithValue(context.Background(), ctrld.ReqIdCtxKey{}, reqId) + if !listenerConfig.AllowWanClients && isWanClient(w.RemoteAddr()) { + ctrld.Log(ctx, mainLog.Load().Debug(), "query refused, listener does not allow WAN clients: %s", w.RemoteAddr().String()) + answer := new(dns.Msg) + answer.SetRcode(m, dns.RcodeRefused) + _ = w.WriteMsg(answer) return } go p.detectLoop(m) q := m.Question[0] domain := canonicalName(q.Name) - reqId := requestID() remoteIP, _, _ := net.SplitHostPort(w.RemoteAddr().String()) ci := p.getClientInfo(remoteIP, m) ci.ClientIDPref = p.cfg.Service.ClientIDPref @@ -90,7 +99,6 @@ func (p *prog) serveDNS(listenerNum string) error { remoteAddr := spoofRemoteAddr(w.RemoteAddr(), ci) fmtSrcToDest := fmtRemoteToLocal(listenerNum, remoteAddr.String(), w.LocalAddr().String()) t := time.Now() - ctx := context.WithValue(context.Background(), ctrld.ReqIdCtxKey{}, reqId) ctrld.Log(ctx, mainLog.Load().Debug(), "%s received query: %s %s", fmtSrcToDest, dns.TypeToString[q.Qtype], domain) res := p.upstreamFor(ctx, listenerNum, listenerConfig, remoteAddr, ci.Mac, domain) var answer *dns.Msg @@ -113,7 +121,7 @@ func (p *prog) serveDNS(listenerNum string) error { ctrld.Log(ctx, mainLog.Load().Debug(), "received response of %d bytes in %s", answer.Len(), rtt) } if err := w.WriteMsg(answer); err != nil { - ctrld.Log(ctx, mainLog.Load().Error().Err(err), "serveUDP: failed to send DNS response to client") + ctrld.Log(ctx, mainLog.Load().Error().Err(err), "serveDNS: failed to send DNS response to client") } }) @@ -865,3 +873,16 @@ func isLanHostnameQuery(m *dns.Msg) bool { strings.HasSuffix(name, ".domain") || strings.HasSuffix(name, ".lan") } + +// isWanClient reports whether the input is a WAN address. +func isWanClient(na net.Addr) bool { + var ip netip.Addr + if ap, err := netip.ParseAddrPort(na.String()); err == nil { + ip = ap.Addr() + } + return !ip.IsLoopback() && + !ip.IsPrivate() && + !ip.IsLinkLocalUnicast() && + !ip.IsLinkLocalMulticast() && + !tsaddr.CGNATRange().Contains(ip) +} diff --git a/cmd/cli/dns_proxy_test.go b/cmd/cli/dns_proxy_test.go index 82c4f63..bd73d17 100644 --- a/cmd/cli/dns_proxy_test.go +++ b/cmd/cli/dns_proxy_test.go @@ -405,3 +405,29 @@ func Test_isPrivatePtrLookup(t *testing.T) { }) } } + +func Test_isWanClient(t *testing.T) { + tests := []struct { + name string + addr net.Addr + isWanClient bool + }{ + // RFC 1918 allocates 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 as + {"10.0.0.0/8", &net.UDPAddr{IP: net.ParseIP("10.0.0.123")}, false}, + {"172.16.0.0/12", &net.UDPAddr{IP: net.ParseIP("172.16.0.123")}, false}, + {"192.168.0.0/16", &net.UDPAddr{IP: net.ParseIP("192.168.1.123")}, false}, + {"CGNAT", &net.UDPAddr{IP: net.ParseIP("100.66.27.28")}, false}, + {"Loopback", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")}, false}, + {"Link Local Unicast", &net.UDPAddr{IP: net.ParseIP("fe80::69f6:e16e:8bdb:433f")}, false}, + {"Public", &net.UDPAddr{IP: net.ParseIP("8.8.8.8")}, true}, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if got := isWanClient(tc.addr); tc.isWanClient != got { + t.Errorf("unexpected result, want: %v, got: %v", tc.isWanClient, got) + } + }) + } +} diff --git a/config.go b/config.go index aeb6e27..5baa10d 100644 --- a/config.go +++ b/config.go @@ -240,10 +240,11 @@ type UpstreamConfig struct { // ListenerConfig specifies the networks configuration that ctrld will run on. type ListenerConfig struct { - IP string `mapstructure:"ip" toml:"ip,omitempty" validate:"iporempty"` - Port int `mapstructure:"port" toml:"port,omitempty" validate:"gte=0"` - Restricted bool `mapstructure:"restricted" toml:"restricted,omitempty"` - Policy *ListenerPolicyConfig `mapstructure:"policy" toml:"policy,omitempty"` + IP string `mapstructure:"ip" toml:"ip,omitempty" validate:"iporempty"` + Port int `mapstructure:"port" toml:"port,omitempty" validate:"gte=0"` + Restricted bool `mapstructure:"restricted" toml:"restricted,omitempty"` + AllowWanClients bool `mapstructure:"allow_wan_clients" toml:"allow_wan_clients,omitempty"` + Policy *ListenerPolicyConfig `mapstructure:"policy" toml:"policy,omitempty"` } // IsDirectDnsListener reports whether ctrld can be a direct listener on port 53. diff --git a/docs/config.md b/docs/config.md index 81e67ee..e5b3945 100644 --- a/docs/config.md +++ b/docs/config.md @@ -405,7 +405,14 @@ Port number that the listener will listen on for incoming requests. If `port` is - Default: 0 or 53 or 5354 (depending on platform) ### restricted -If set to `true` makes the listener `REFUSE` DNS queries from all source IP addresses that are not explicitly defined in the policy using a `network`. +If set to `true`, makes the listener `REFUSED` DNS queries from all source IP addresses that are not explicitly defined in the policy using a `network`. + +- Type: bool +- Required: no +- Default: false + +### allow_wan_clients +The listener `REFUSED` DNS queries from WAN clients by default. If set to `true`, makes the listener replies to them. - Type: bool - Required: no