all: sync with master; upd chlog
This commit is contained in:
@@ -67,7 +67,7 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
}
|
||||
|
||||
host := fields[0]
|
||||
err = netutil.ValidateDomainName(host)
|
||||
err = netutil.ValidateHostname(host)
|
||||
if err != nil {
|
||||
log.Debug("arpdb: parsing arp output: host: %s", err)
|
||||
} else {
|
||||
|
||||
@@ -198,7 +198,7 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
}
|
||||
|
||||
host := fields[0]
|
||||
if verr := netutil.ValidateDomainName(host); verr != nil {
|
||||
if verr := netutil.ValidateHostname(host); verr != nil {
|
||||
log.Debug("arpdb: parsing arp output: host: %s", verr)
|
||||
} else {
|
||||
n.Name = host
|
||||
|
||||
@@ -343,7 +343,7 @@ func (hp *hostsParser) parseLine(line string) (ip netip.Addr, hosts []string) {
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/3946.
|
||||
//
|
||||
// TODO(e.burkov): Investigate if hosts may contain DNS-SD domains.
|
||||
err = netutil.ValidateDomainName(f)
|
||||
err = netutil.ValidateHostname(f)
|
||||
if err != nil {
|
||||
log.Error("%s: host %q is invalid, ignoring", hostsContainerPref, f)
|
||||
|
||||
|
||||
@@ -45,8 +45,10 @@ type DHCPServer interface {
|
||||
AddStaticLease(l *Lease) (err error)
|
||||
// RemoveStaticLease - remove a static lease
|
||||
RemoveStaticLease(l *Lease) (err error)
|
||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||
FindMACbyIP(ip net.IP) net.HardwareAddr
|
||||
|
||||
// FindMACbyIP returns a MAC address by the IP address of its lease, if
|
||||
// there is one.
|
||||
FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
|
||||
// WriteDiskConfig4 - copy disk configuration
|
||||
WriteDiskConfig4(c *V4ServerConf)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@@ -42,7 +43,11 @@ type Lease struct {
|
||||
|
||||
Hostname string `json:"hostname"`
|
||||
HWAddr net.HardwareAddr `json:"mac"`
|
||||
IP net.IP `json:"ip"`
|
||||
|
||||
// IP is the IP address leased to the client.
|
||||
//
|
||||
// TODO(a.garipov): Migrate leases.db and use netip.Addr.
|
||||
IP net.IP `json:"ip"`
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of l.
|
||||
@@ -160,7 +165,7 @@ type Interface interface {
|
||||
|
||||
Leases(flags GetLeasesFlags) (leases []*Lease)
|
||||
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
|
||||
FindMACbyIP(ip net.IP) (mac net.HardwareAddr)
|
||||
FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
|
||||
WriteDiskConfig(c *ServerConfig)
|
||||
}
|
||||
@@ -174,7 +179,7 @@ type MockInterface struct {
|
||||
OnEnabled func() (ok bool)
|
||||
OnLeases func(flags GetLeasesFlags) (leases []*Lease)
|
||||
OnSetOnLeaseChanged func(f OnLeaseChangedT)
|
||||
OnFindMACbyIP func(ip net.IP) (mac net.HardwareAddr)
|
||||
OnFindMACbyIP func(ip netip.Addr) (mac net.HardwareAddr)
|
||||
OnWriteDiskConfig func(c *ServerConfig)
|
||||
}
|
||||
|
||||
@@ -195,8 +200,10 @@ func (s *MockInterface) Leases(flags GetLeasesFlags) (ls []*Lease) { return s.On
|
||||
// SetOnLeaseChanged implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) SetOnLeaseChanged(f OnLeaseChangedT) { s.OnSetOnLeaseChanged(f) }
|
||||
|
||||
// FindMACbyIP implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) FindMACbyIP(ip net.IP) (mac net.HardwareAddr) { return s.OnFindMACbyIP(ip) }
|
||||
// FindMACbyIP implements the [Interface] for *MockInterface.
|
||||
func (s *MockInterface) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
return s.OnFindMACbyIP(ip)
|
||||
}
|
||||
|
||||
// WriteDiskConfig implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) WriteDiskConfig(c *ServerConfig) { s.OnWriteDiskConfig(c) }
|
||||
@@ -375,11 +382,13 @@ func (s *server) Leases(flags GetLeasesFlags) (leases []*Lease) {
|
||||
return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...)
|
||||
}
|
||||
|
||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||
func (s *server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
||||
if ip.To4() != nil {
|
||||
// FindMACbyIP returns a MAC address by the IP address of its lease, if there is
|
||||
// one.
|
||||
func (s *server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
if ip.Is4() {
|
||||
return s.srv4.FindMACbyIP(ip)
|
||||
}
|
||||
|
||||
return s.srv6.FindMACbyIP(ip)
|
||||
}
|
||||
|
||||
|
||||
@@ -263,15 +263,12 @@ func (s *v4Server) prepareOptions() {
|
||||
|
||||
// IP-Layer Per Interface
|
||||
|
||||
// Since nearly all networks in the Internet currently support an MTU of
|
||||
// 576 or greater, we strongly recommend the use of 576 for datagrams
|
||||
// sent to non-local networks.
|
||||
// Don't set the Interface MTU because client may choose the value on
|
||||
// their own since it's listed in the [Host Requirements RFC]. It also
|
||||
// seems the values listed there sometimes appear obsolete, see
|
||||
// https://github.com/AdguardTeam/AdGuardHome/issues/5281.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.3.
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionInterfaceMTU,
|
||||
Value: dhcpv4.Uint16(576),
|
||||
},
|
||||
// [Host Requirements RFC]: https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.3.
|
||||
|
||||
// Set the All Subnets Are Local Option to false since commonly the
|
||||
// connected hosts aren't expected to be multihomed.
|
||||
|
||||
@@ -4,23 +4,26 @@ package dhcpd
|
||||
|
||||
// 'u-root/u-root' package, a dependency of 'insomniacslk/dhcp' package, doesn't build on Windows
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type winServer struct{}
|
||||
|
||||
// type check
|
||||
var _ DHCPServer = winServer{}
|
||||
|
||||
func (winServer) ResetLeases(_ []*Lease) (err error) { return nil }
|
||||
func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil }
|
||||
func (winServer) getLeasesRef() []*Lease { return nil }
|
||||
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) RemoveStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) FindMACbyIP(_ net.IP) (mac net.HardwareAddr) { return nil }
|
||||
func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
|
||||
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
|
||||
func (winServer) Start() (err error) { return nil }
|
||||
func (winServer) Stop() (err error) { return nil }
|
||||
func (winServer) ResetLeases(_ []*Lease) (err error) { return nil }
|
||||
func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil }
|
||||
func (winServer) getLeasesRef() []*Lease { return nil }
|
||||
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) RemoveStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
|
||||
func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
|
||||
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
|
||||
func (winServer) Start() (err error) { return nil }
|
||||
func (winServer) Stop() (err error) { return nil }
|
||||
|
||||
func v4Create(_ *V4ServerConf) (s DHCPServer, err error) { return winServer{}, nil }
|
||||
func v6Create(_ V6ServerConf) (s DHCPServer, err error) { return winServer{}, nil }
|
||||
|
||||
@@ -108,7 +108,7 @@ func (s *v4Server) validHostnameForClient(cliHostname string, ip net.IP) (hostna
|
||||
hostname = aghnet.GenerateHostname(ip)
|
||||
}
|
||||
|
||||
err = netutil.ValidateDomainName(hostname)
|
||||
err = netutil.ValidateHostname(hostname)
|
||||
if err != nil {
|
||||
log.Info("dhcpv4: %s", err)
|
||||
hostname = ""
|
||||
@@ -200,20 +200,20 @@ func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
|
||||
return leases
|
||||
}
|
||||
|
||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||
func (s *v4Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
||||
// FindMACbyIP implements the [Interface] for *v4Server.
|
||||
func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
now := time.Now()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
ip4 := ip.To4()
|
||||
if ip4 == nil {
|
||||
if !ip.Is4() {
|
||||
return nil
|
||||
}
|
||||
|
||||
netIP := ip.AsSlice()
|
||||
for _, l := range s.leases {
|
||||
if l.IP.Equal(ip4) {
|
||||
if l.IP.Equal(netIP) {
|
||||
if l.Expiry.After(now) || l.IsStatic() {
|
||||
return l.HWAddr
|
||||
}
|
||||
@@ -372,7 +372,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = netutil.ValidateDomainName(hostname)
|
||||
err = netutil.ValidateHostname(hostname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating hostname: %w", err)
|
||||
}
|
||||
|
||||
@@ -251,8 +251,8 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
||||
},
|
||||
name: "bad_hostname",
|
||||
wantErrMsg: `dhcpv4: adding static lease: validating hostname: ` +
|
||||
`bad domain name "bad-lbl-.local": ` +
|
||||
`bad domain name label "bad-lbl-": bad domain name label rune '-'`,
|
||||
`bad hostname "bad-lbl-.local": ` +
|
||||
`bad hostname label "bad-lbl-": bad hostname label rune '-'`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -107,21 +108,26 @@ func (s *v6Server) getLeasesRef() []*Lease {
|
||||
return s.leases
|
||||
}
|
||||
|
||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||
func (s *v6Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
||||
now := time.Now().Unix()
|
||||
// FindMACbyIP implements the [Interface] for *v6Server.
|
||||
func (s *v6Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
now := time.Now()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
if !ip.Is6() {
|
||||
return nil
|
||||
}
|
||||
|
||||
netIP := ip.AsSlice()
|
||||
for _, l := range s.leases {
|
||||
if l.IP.Equal(ip) {
|
||||
unix := l.Expiry.Unix()
|
||||
if unix > now || unix == leaseExpireStatic {
|
||||
if l.IP.Equal(netIP) {
|
||||
if l.Expiry.After(now) || l.IsStatic() {
|
||||
return l.HWAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
)
|
||||
|
||||
// unit is a convenient alias for struct{}
|
||||
@@ -127,8 +128,12 @@ func (a *accessManager) isBlockedClientID(id string) (ok bool) {
|
||||
}
|
||||
|
||||
// isBlockedHost returns true if host should be blocked.
|
||||
func (a *accessManager) isBlockedHost(host string) (ok bool) {
|
||||
_, ok = a.blockedHostsEng.Match(strings.ToLower(host))
|
||||
func (a *accessManager) isBlockedHost(host string, qt rules.RRType) (ok bool) {
|
||||
_, ok = a.blockedHostsEng.MatchRequest(&urlfilter.DNSRequest{
|
||||
Hostname: host,
|
||||
ClientIP: "0.0.0.0",
|
||||
DNSType: qt,
|
||||
})
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -28,54 +30,75 @@ func TestIsBlockedHost(t *testing.T) {
|
||||
"host1",
|
||||
"*.host.com",
|
||||
"||host3.com^",
|
||||
"||*^$dnstype=HTTPS",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
want assert.BoolAssertionFunc
|
||||
name string
|
||||
host string
|
||||
want bool
|
||||
qt rules.RRType
|
||||
}{{
|
||||
want: assert.True,
|
||||
name: "plain_match",
|
||||
host: "host1",
|
||||
want: true,
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "plain_mismatch",
|
||||
host: "host2",
|
||||
want: false,
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: assert.True,
|
||||
name: "subdomain_match_short",
|
||||
host: "asdf.host.com",
|
||||
want: true,
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: assert.True,
|
||||
name: "subdomain_match_long",
|
||||
host: "qwer.asdf.host.com",
|
||||
want: true,
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "subdomain_mismatch_no_lead",
|
||||
host: "host.com",
|
||||
want: false,
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "subdomain_mismatch_bad_asterisk",
|
||||
host: "asdf.zhost.com",
|
||||
want: false,
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: assert.True,
|
||||
name: "rule_match_simple",
|
||||
host: "host3.com",
|
||||
want: true,
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: assert.True,
|
||||
name: "rule_match_complex",
|
||||
host: "asdf.host3.com",
|
||||
want: true,
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "rule_mismatch",
|
||||
host: ".host3.com",
|
||||
want: false,
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: assert.True,
|
||||
name: "by_qtype",
|
||||
host: "site-with-https-record.example",
|
||||
qt: dns.TypeHTTPS,
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "by_qtype_other",
|
||||
host: "site-with-https-record.example",
|
||||
qt: dns.TypeA,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, a.isBlockedHost(tc.host))
|
||||
tc.want(t, a.isBlockedHost(tc.host, tc.qt))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -93,29 +116,29 @@ func TestIsBlockedIP(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
ip netip.Addr
|
||||
name string
|
||||
wantRule string
|
||||
ip netip.Addr
|
||||
wantBlocked bool
|
||||
}{{
|
||||
ip: netip.MustParseAddr("1.2.3.4"),
|
||||
name: "match_ip",
|
||||
wantRule: "1.2.3.4",
|
||||
ip: netip.MustParseAddr("1.2.3.4"),
|
||||
wantBlocked: true,
|
||||
}, {
|
||||
ip: netip.MustParseAddr("5.6.7.100"),
|
||||
name: "match_cidr",
|
||||
wantRule: "5.6.7.8/24",
|
||||
ip: netip.MustParseAddr("5.6.7.100"),
|
||||
wantBlocked: true,
|
||||
}, {
|
||||
ip: netip.MustParseAddr("9.2.3.4"),
|
||||
name: "no_match_ip",
|
||||
wantRule: "",
|
||||
ip: netip.MustParseAddr("9.2.3.4"),
|
||||
wantBlocked: false,
|
||||
}, {
|
||||
ip: netip.MustParseAddr("9.6.7.100"),
|
||||
name: "no_match_cidr",
|
||||
wantRule: "",
|
||||
ip: netip.MustParseAddr("9.6.7.100"),
|
||||
wantBlocked: false,
|
||||
}}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
// ValidateClientID returns an error if id is not a valid ClientID.
|
||||
func ValidateClientID(id string) (err error) {
|
||||
err = netutil.ValidateDomainNameLabel(id)
|
||||
err = netutil.ValidateHostnameLabel(id)
|
||||
if err != nil {
|
||||
// Replace the domain name label wrapper with our own.
|
||||
return fmt.Errorf("invalid clientid %q: %w", id, errors.Unwrap(err))
|
||||
|
||||
@@ -119,7 +119,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
|
||||
cliSrvName: "!!!.example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `clientid check: invalid clientid "!!!": ` +
|
||||
`bad domain name label rune '!'`,
|
||||
`bad hostname label rune '!'`,
|
||||
inclHTTPTLS: false,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
@@ -131,7 +131,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
|
||||
wantClientID: "",
|
||||
wantErrMsg: `clientid check: invalid clientid "abcdefghijklmno` +
|
||||
`pqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789": ` +
|
||||
`domain name label is too long: got 72, max 63`,
|
||||
`hostname label is too long: got 72, max 63`,
|
||||
inclHTTPTLS: false,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
@@ -330,7 +330,7 @@ func TestClientIDFromDNSContextHTTPS(t *testing.T) {
|
||||
path: "/dns-query/!!!",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `clientid check: invalid clientid "!!!": bad domain name label rune '!'`,
|
||||
wantErrMsg: `clientid check: invalid clientid "!!!": bad hostname label rune '!'`,
|
||||
}, {
|
||||
name: "both_ids",
|
||||
path: "/dns-query/right",
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -23,6 +22,7 @@ import (
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// BlockingMode is an enum of all allowed blocking modes.
|
||||
@@ -53,7 +53,6 @@ const (
|
||||
// The zero FilteringConfig is empty and ready for use.
|
||||
type FilteringConfig struct {
|
||||
// Callbacks for other modules
|
||||
// --
|
||||
|
||||
// FilterHandler is an optional additional filtering callback.
|
||||
FilterHandler func(clientAddr net.IP, clientID string, settings *filtering.Settings) `yaml:"-"`
|
||||
@@ -64,50 +63,82 @@ type FilteringConfig struct {
|
||||
GetCustomUpstreamByClient func(id string) (conf *proxy.UpstreamConfig, err error) `yaml:"-"`
|
||||
|
||||
// Protection configuration
|
||||
// --
|
||||
|
||||
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of filtering features
|
||||
BlockingMode BlockingMode `yaml:"blocking_mode"` // mode how to answer filtered requests
|
||||
BlockingIPv4 net.IP `yaml:"blocking_ipv4"` // IP address to be returned for a blocked A request
|
||||
BlockingIPv6 net.IP `yaml:"blocking_ipv6"` // IP address to be returned for a blocked AAAA request
|
||||
BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600)
|
||||
// ProtectionEnabled defines whether or not use any of filtering features.
|
||||
ProtectionEnabled bool `yaml:"protection_enabled"`
|
||||
|
||||
// IP (or domain name) which is used to respond to DNS requests blocked by parental control or safe-browsing
|
||||
ParentalBlockHost string `yaml:"parental_block_host"`
|
||||
// BlockingMode defines the way how blocked responses are constructed.
|
||||
BlockingMode BlockingMode `yaml:"blocking_mode"`
|
||||
|
||||
// BlockingIPv4 is the IP address to be returned for a blocked A request.
|
||||
BlockingIPv4 net.IP `yaml:"blocking_ipv4"`
|
||||
|
||||
// BlockingIPv6 is the IP address to be returned for a blocked AAAA
|
||||
// request.
|
||||
BlockingIPv6 net.IP `yaml:"blocking_ipv6"`
|
||||
|
||||
// BlockedResponseTTL is the time-to-live value for blocked responses. If
|
||||
// 0, then default value is used (3600).
|
||||
BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"`
|
||||
|
||||
// ParentalBlockHost is the IP (or domain name) which is used to respond to
|
||||
// DNS requests blocked by parental control.
|
||||
ParentalBlockHost string `yaml:"parental_block_host"`
|
||||
|
||||
// SafeBrowsingBlockHost is the IP (or domain name) which is used to
|
||||
// respond to DNS requests blocked by safe-browsing.
|
||||
SafeBrowsingBlockHost string `yaml:"safebrowsing_block_host"`
|
||||
|
||||
// Anti-DNS amplification
|
||||
// --
|
||||
|
||||
Ratelimit uint32 `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable)
|
||||
RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` // a list of whitelisted client IP addresses
|
||||
RefuseAny bool `yaml:"refuse_any"` // if true, refuse ANY requests
|
||||
// Ratelimit is the maximum number of requests per second from a given IP
|
||||
// (0 to disable).
|
||||
Ratelimit uint32 `yaml:"ratelimit"`
|
||||
|
||||
// RatelimitWhitelist is the list of whitelisted client IP addresses.
|
||||
RatelimitWhitelist []string `yaml:"ratelimit_whitelist"`
|
||||
|
||||
// RefuseAny, if true, refuse ANY requests.
|
||||
RefuseAny bool `yaml:"refuse_any"`
|
||||
|
||||
// Upstream DNS servers configuration
|
||||
// --
|
||||
|
||||
UpstreamDNS []string `yaml:"upstream_dns"`
|
||||
UpstreamDNSFileName string `yaml:"upstream_dns_file"`
|
||||
BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only)
|
||||
AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled
|
||||
FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm
|
||||
// UpstreamDNS is the list of upstream DNS servers.
|
||||
UpstreamDNS []string `yaml:"upstream_dns"`
|
||||
|
||||
// UpstreamDNSFileName, if set, points to the file which contains upstream
|
||||
// DNS servers.
|
||||
UpstreamDNSFileName string `yaml:"upstream_dns_file"`
|
||||
|
||||
// BootstrapDNS is the list of bootstrap DNS servers for DoH and DoT
|
||||
// resolvers (plain DNS only).
|
||||
BootstrapDNS []string `yaml:"bootstrap_dns"`
|
||||
|
||||
// AllServers, if true, parallel queries to all configured upstream servers
|
||||
// are enabled.
|
||||
AllServers bool `yaml:"all_servers"`
|
||||
|
||||
// FastestAddr, if true, use Fastest Address algorithm.
|
||||
FastestAddr bool `yaml:"fastest_addr"`
|
||||
|
||||
// FastestTimeout replaces the default timeout for dialing IP addresses
|
||||
// when FastestAddr is true.
|
||||
FastestTimeout timeutil.Duration `yaml:"fastest_timeout"`
|
||||
|
||||
// Access settings
|
||||
// --
|
||||
|
||||
// AllowedClients is the slice of IP addresses, CIDR networks, and ClientIDs
|
||||
// of allowed clients. If not empty, only these clients are allowed, and
|
||||
// [FilteringConfig.DisallowedClients] are ignored.
|
||||
// AllowedClients is the slice of IP addresses, CIDR networks, and
|
||||
// ClientIDs of allowed clients. If not empty, only these clients are
|
||||
// allowed, and [FilteringConfig.DisallowedClients] are ignored.
|
||||
AllowedClients []string `yaml:"allowed_clients"`
|
||||
|
||||
// DisallowedClients is the slice of IP addresses, CIDR networks, and
|
||||
// ClientIDs of disallowed clients.
|
||||
DisallowedClients []string `yaml:"disallowed_clients"`
|
||||
|
||||
BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked
|
||||
// BlockedHosts is the list of hosts that should be blocked.
|
||||
BlockedHosts []string `yaml:"blocked_hosts"`
|
||||
|
||||
// TrustedProxies is the list of IP addresses and CIDR networks to detect
|
||||
// proxy servers addresses the DoH requests from which should be handled.
|
||||
// The value of nil or an empty slice for this field makes Proxy not trust
|
||||
@@ -115,26 +146,46 @@ type FilteringConfig struct {
|
||||
TrustedProxies []string `yaml:"trusted_proxies"`
|
||||
|
||||
// DNS cache settings
|
||||
// --
|
||||
|
||||
CacheSize uint32 `yaml:"cache_size"` // DNS cache size (in bytes)
|
||||
CacheMinTTL uint32 `yaml:"cache_ttl_min"` // override TTL value (minimum) received from upstream server
|
||||
CacheMaxTTL uint32 `yaml:"cache_ttl_max"` // override TTL value (maximum) received from upstream server
|
||||
// CacheSize is the DNS cache size (in bytes).
|
||||
CacheSize uint32 `yaml:"cache_size"`
|
||||
|
||||
// CacheMinTTL is the override TTL value (minimum) received from upstream
|
||||
// server.
|
||||
CacheMinTTL uint32 `yaml:"cache_ttl_min"`
|
||||
|
||||
// CacheMaxTTL is the override TTL value (maximum) received from upstream
|
||||
// server.
|
||||
CacheMaxTTL uint32 `yaml:"cache_ttl_max"`
|
||||
|
||||
// CacheOptimistic defines if optimistic cache mechanism should be used.
|
||||
CacheOptimistic bool `yaml:"cache_optimistic"`
|
||||
|
||||
// Other settings
|
||||
// --
|
||||
|
||||
BogusNXDomain []string `yaml:"bogus_nxdomain"` // transform responses with these IP addresses to NXDOMAIN
|
||||
AAAADisabled bool `yaml:"aaaa_disabled"` // Respond with an empty answer to all AAAA requests
|
||||
EnableDNSSEC bool `yaml:"enable_dnssec"` // Set AD flag in outcoming DNS request
|
||||
EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
|
||||
MaxGoroutines uint32 `yaml:"max_goroutines"` // Max. number of parallel goroutines for processing incoming requests
|
||||
HandleDDR bool `yaml:"handle_ddr"` // Handle DDR requests
|
||||
// BogusNXDomain is the list of IP addresses, responses with them will be
|
||||
// transformed to NXDOMAIN.
|
||||
BogusNXDomain []string `yaml:"bogus_nxdomain"`
|
||||
|
||||
// IpsetList is the ipset configuration that allows AdGuard Home to add
|
||||
// IP addresses of the specified domain names to an ipset list. Syntax:
|
||||
// AAAADisabled, if true, respond with an empty answer to all AAAA
|
||||
// requests.
|
||||
AAAADisabled bool `yaml:"aaaa_disabled"`
|
||||
|
||||
// EnableDNSSEC, if true, set AD flag in outcoming DNS request.
|
||||
EnableDNSSEC bool `yaml:"enable_dnssec"`
|
||||
|
||||
// EDNSClientSubnet is the settings list for EDNS Client Subnet.
|
||||
EDNSClientSubnet *EDNSClientSubnet `yaml:"edns_client_subnet"`
|
||||
|
||||
// MaxGoroutines is the max number of parallel goroutines for processing
|
||||
// incoming requests.
|
||||
MaxGoroutines uint32 `yaml:"max_goroutines"`
|
||||
|
||||
// HandleDDR, if true, handle DDR requests
|
||||
HandleDDR bool `yaml:"handle_ddr"`
|
||||
|
||||
// IpsetList is the ipset configuration that allows AdGuard Home to add IP
|
||||
// addresses of the specified domain names to an ipset list. Syntax:
|
||||
//
|
||||
// DOMAIN[,DOMAIN].../IPSET_NAME
|
||||
//
|
||||
@@ -146,6 +197,18 @@ type FilteringConfig struct {
|
||||
IpsetListFileName string `yaml:"ipset_file"`
|
||||
}
|
||||
|
||||
// EDNSClientSubnet is the settings list for EDNS Client Subnet.
|
||||
type EDNSClientSubnet struct {
|
||||
// CustomIP for EDNS Client Subnet.
|
||||
CustomIP string `yaml:"custom_ip"`
|
||||
|
||||
// Enabled defines if EDNS Client Subnet is enabled.
|
||||
Enabled bool `yaml:"enabled"`
|
||||
|
||||
// UseCustom defines if CustomIP should be used.
|
||||
UseCustom bool `yaml:"use_custom"`
|
||||
}
|
||||
|
||||
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
|
||||
type TLSConfig struct {
|
||||
cert tls.Certificate
|
||||
@@ -270,12 +333,24 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
BeforeRequestHandler: s.beforeRequestHandler,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
EnableEDNSClientSubnet: srvConf.EnableEDNSClientSubnet,
|
||||
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
|
||||
MaxGoroutines: int(srvConf.MaxGoroutines),
|
||||
UseDNS64: srvConf.UseDNS64,
|
||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||
}
|
||||
|
||||
if srvConf.EDNSClientSubnet.UseCustom {
|
||||
// TODO(s.chzhen): Add wrapper around netip.Addr.
|
||||
var ip net.IP
|
||||
ip, err = netutil.ParseIP(srvConf.EDNSClientSubnet.CustomIP)
|
||||
if err != nil {
|
||||
return conf, fmt.Errorf("edns: %w", err)
|
||||
}
|
||||
|
||||
// TODO(s.chzhen): Use netip.Addr instead of net.IP inside dnsproxy.
|
||||
conf.EDNSAddr = ip
|
||||
}
|
||||
|
||||
if srvConf.CacheSize != 0 {
|
||||
conf.CacheEnabled = true
|
||||
conf.CacheSizeBytes = int(srvConf.CacheSize)
|
||||
@@ -510,7 +585,7 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) (err error) {
|
||||
if len(cert.DNSNames) != 0 {
|
||||
s.conf.dnsNames = cert.DNSNames
|
||||
log.Debug("dnsforward: using certificate's SAN as DNS names: %v", cert.DNSNames)
|
||||
sort.Strings(s.conf.dnsNames)
|
||||
slices.Sort(s.conf.dnsNames)
|
||||
} else {
|
||||
s.conf.dnsNames = append(s.conf.dnsNames, cert.Subject.CommonName)
|
||||
log.Debug("dnsforward: using certificate's CN as DNS name: %s", cert.Subject.CommonName)
|
||||
@@ -526,16 +601,6 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// isInSorted returns true if s is in the sorted slice strs.
|
||||
func isInSorted(strs []string, s string) (ok bool) {
|
||||
i := sort.SearchStrings(strs, s)
|
||||
if i == len(strs) || strs[i] != s {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isWildcard returns true if host is a wildcard hostname.
|
||||
func isWildcard(host string) (ok bool) {
|
||||
return len(host) >= 2 && host[0] == '*' && host[1] == '.'
|
||||
@@ -550,11 +615,12 @@ func matchesDomainWildcard(host, pat string) (ok bool) {
|
||||
// anyNameMatches returns true if sni, the client's SNI value, matches any of
|
||||
// the DNS names and patterns from certificate. dnsNames must be sorted.
|
||||
func anyNameMatches(dnsNames []string, sni string) (ok bool) {
|
||||
if netutil.ValidateDomainName(sni) != nil {
|
||||
// Check sni is either a valid hostname or a valid IP address.
|
||||
if netutil.ValidateHostname(sni) != nil && net.ParseIP(sni) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if isInSorted(dnsNames, sni) {
|
||||
if _, ok = slices.BinarySearch(dnsNames, sni); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func TestAnyNameMatches(t *testing.T) {
|
||||
dnsNames := []string{"host1", "*.host2", "1.2.3.4"}
|
||||
sort.Strings(dnsNames)
|
||||
slices.Sort(dnsNames)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -31,6 +31,10 @@ func TestAnyNameMatches(t *testing.T) {
|
||||
name: "match",
|
||||
dnsName: "1.2.3.4",
|
||||
want: true,
|
||||
}, {
|
||||
name: "mismatch_bad_ip",
|
||||
dnsName: "1.2.3.256",
|
||||
want: false,
|
||||
}, {
|
||||
name: "mismatch",
|
||||
dnsName: "host2",
|
||||
|
||||
@@ -230,7 +230,7 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||
for _, l := range ll {
|
||||
// TODO(a.garipov): Remove this after we're finished with the client
|
||||
// hostname validations in the DHCP server code.
|
||||
err := netutil.ValidateDomainName(l.Hostname)
|
||||
err := netutil.ValidateHostname(l.Hostname)
|
||||
if err != nil {
|
||||
log.Debug("dnsforward: skipping invalid hostname %q from dhcp: %s", l.Hostname, err)
|
||||
|
||||
@@ -468,7 +468,7 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: request is for a service domain")
|
||||
log.Debug("dnsforward: request is not for arpa domain")
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
@@ -273,18 +273,25 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) {
|
||||
return resp, nil
|
||||
})
|
||||
|
||||
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
UseDNS64: true,
|
||||
}, localUps)
|
||||
|
||||
client := &dns.Client{
|
||||
Net: "tcp",
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
// TODO(e.burkov): It seems [proxy.Proxy] isn't intended to be reused
|
||||
// right after stop, due to a data race in [proxy.Proxy.Init] method
|
||||
// when setting an OOB size. As a temporary workaround, recreate the
|
||||
// whole server for each test case.
|
||||
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
UseDNS64: true,
|
||||
FilteringConfig: FilteringConfig{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
}, localUps)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newUps(tc.upsAns)}
|
||||
startDeferStop(t, s)
|
||||
|
||||
@@ -467,6 +467,11 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
// TODO(s.chzhen): Add tests where EDNSClientSubnet.Enabled is true.
|
||||
// Improve FilteringConfig declaration for tests.
|
||||
FilteringConfig: FilteringConfig{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
}, ups)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ups}
|
||||
startDeferStop(t, s)
|
||||
@@ -539,6 +544,9 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
|
||||
ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
},
|
||||
aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -155,6 +156,9 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte)
|
||||
s = createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem
|
||||
@@ -266,6 +270,9 @@ func TestServer(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
}, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
@@ -304,7 +311,8 @@ func TestServer_timeout(t *testing.T) {
|
||||
srvConf := &ServerConfig{
|
||||
UpstreamTimeout: timeout,
|
||||
FilteringConfig: FilteringConfig{
|
||||
BlockingMode: BlockingModeDefault,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -322,6 +330,9 @@ func TestServer_timeout(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
|
||||
s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
}
|
||||
err = s.Prepare(&s.conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -333,6 +344,9 @@ func TestServerWithProtectionDisabled(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
}, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
@@ -437,6 +451,9 @@ func TestSafeSearch(t *testing.T) {
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
@@ -492,6 +509,11 @@ func TestInvalidRequest(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
startDeferStop(t, s)
|
||||
|
||||
@@ -518,6 +540,9 @@ func TestBlockedRequest(t *testing.T) {
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
|
||||
@@ -543,6 +568,9 @@ func TestServerCustomClientUpstream(t *testing.T) {
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
|
||||
@@ -591,6 +619,11 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
testUpstm := &aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
@@ -621,6 +654,9 @@ func TestBlockCNAME(t *testing.T) {
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
|
||||
@@ -690,6 +726,9 @@ func TestClientRulesForCNAMEMatching(t *testing.T) {
|
||||
FilterHandler: func(_ net.IP, _ string, settings *filtering.Settings) {
|
||||
settings.FilteringEnabled = false
|
||||
},
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
|
||||
@@ -731,6 +770,9 @@ func TestNullBlockedRequest(t *testing.T) {
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeNullIP,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
|
||||
@@ -783,6 +825,9 @@ func TestBlockedCustomIP(t *testing.T) {
|
||||
BlockingMode: BlockingModeCustomIP,
|
||||
BlockingIPv4: nil,
|
||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -831,6 +876,9 @@ func TestBlockedByHosts(t *testing.T) {
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -864,6 +912,9 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
|
||||
FilteringConfig: FilteringConfig{
|
||||
SafeBrowsingBlockHost: ans4.String(),
|
||||
ProtectionEnabled: true,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
@@ -918,6 +969,9 @@ func TestRewrite(t *testing.T) {
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
UpstreamDNS: []string{"8.8.8.8:53"},
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -1009,7 +1063,7 @@ var testDHCP = &dhcpd.MockInterface{
|
||||
}}
|
||||
},
|
||||
OnSetOnLeaseChanged: func(olct dhcpd.OnLeaseChangedT) {},
|
||||
OnFindMACbyIP: func(ip net.IP) (mac net.HardwareAddr) { panic("not implemented") },
|
||||
OnFindMACbyIP: func(ip netip.Addr) (mac net.HardwareAddr) { panic("not implemented") },
|
||||
OnWriteDiskConfig: func(c *dhcpd.ServerConfig) { panic("not implemented") },
|
||||
}
|
||||
|
||||
@@ -1032,6 +1086,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
||||
s.conf.FilteringConfig.ProtectionEnabled = true
|
||||
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
|
||||
s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
|
||||
|
||||
err = s.Prepare(&s.conf)
|
||||
require.NoError(t, err)
|
||||
@@ -1107,6 +1162,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
||||
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
|
||||
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
||||
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
|
||||
s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
|
||||
|
||||
err = s.Prepare(&s.conf)
|
||||
require.NoError(t, err)
|
||||
@@ -1171,7 +1227,8 @@ func TestNewServer(t *testing.T) {
|
||||
LocalDomain: "!!!",
|
||||
},
|
||||
wantErrMsg: `local domain: bad domain name "!!!": ` +
|
||||
`bad domain name label "!!!": bad domain name label rune '!'`,
|
||||
`bad top-level domain name label "!!!": ` +
|
||||
`bad top-level domain name label rune '!'`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -31,9 +31,11 @@ func (s *Server) beforeRequestHandler(
|
||||
}
|
||||
|
||||
if len(pctx.Req.Question) == 1 {
|
||||
host := strings.TrimSuffix(pctx.Req.Question[0].Name, ".")
|
||||
if s.access.isBlockedHost(host) {
|
||||
log.Debug("host %s is in access blocklist", host)
|
||||
q := pctx.Req.Question[0]
|
||||
qt := q.Qtype
|
||||
host := strings.TrimSuffix(q.Name, ".")
|
||||
if s.access.isBlockedHost(host, qt) {
|
||||
log.Debug("request %s %s is in access blocklist", dns.Type(qt), host)
|
||||
|
||||
return s.preBlockedResponse(pctx)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
filters := []filtering.Filter{{
|
||||
|
||||
@@ -57,7 +57,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
|
||||
blockingIPv4 := s.conf.BlockingIPv4
|
||||
blockingIPv6 := s.conf.BlockingIPv6
|
||||
ratelimit := s.conf.Ratelimit
|
||||
enableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet
|
||||
enableEDNSClientSubnet := s.conf.EDNSClientSubnet.Enabled
|
||||
enableDNSSEC := s.conf.EnableDNSSEC
|
||||
aaaaDisabled := s.conf.AAAADisabled
|
||||
cacheSize := s.conf.CacheSize
|
||||
@@ -280,7 +280,7 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
|
||||
setIfNotNil(&s.conf.LocalPTRResolvers, dc.LocalPTRUpstreams),
|
||||
setIfNotNil(&s.conf.UpstreamDNSFileName, dc.UpstreamsFile),
|
||||
setIfNotNil(&s.conf.BootstrapDNS, dc.Bootstraps),
|
||||
setIfNotNil(&s.conf.EnableEDNSClientSubnet, dc.EDNSCSEnabled),
|
||||
setIfNotNil(&s.conf.EDNSClientSubnet.Enabled, dc.EDNSCSEnabled),
|
||||
setIfNotNil(&s.conf.CacheSize, dc.CacheSize),
|
||||
setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL),
|
||||
setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL),
|
||||
|
||||
@@ -69,6 +69,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ConfigModified: func() {},
|
||||
}
|
||||
@@ -144,6 +145,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ConfigModified: func() {},
|
||||
}
|
||||
@@ -227,7 +229,10 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Cleanup(func() { s.conf = defaultConf })
|
||||
t.Cleanup(func() {
|
||||
s.conf = defaultConf
|
||||
s.conf.FilteringConfig.EDNSClientSubnet.Enabled = false
|
||||
})
|
||||
|
||||
rBody := io.NopCloser(bytes.NewReader(caseData.Req))
|
||||
var r *http.Request
|
||||
@@ -337,7 +342,8 @@ func TestValidateUpstreams(t *testing.T) {
|
||||
}, {
|
||||
name: "bad_domain",
|
||||
wantErr: `bad upstream for domain "[/!/]8.8.8.8": domain at index 0: ` +
|
||||
`bad domain name "!": bad domain name label "!": bad domain name label rune '!'`,
|
||||
`bad domain name "!": bad top-level domain name label "!": ` +
|
||||
`bad top-level domain name label rune '!'`,
|
||||
set: []string{"[/!/]8.8.8.8"},
|
||||
}}
|
||||
|
||||
@@ -442,6 +448,9 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
UpstreamTimeout: upsTimeout,
|
||||
FilteringConfig: FilteringConfig{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
}, nil)
|
||||
startDeferStop(t, srv)
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package filtering
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This is a simple tool that takes a list of services and prints them to the output.
|
||||
// It is supposed to be used to update:
|
||||
// client/src/helpers/constants.js
|
||||
// client/src/components/ui/Icons.js
|
||||
//
|
||||
// Usage:
|
||||
// 1. go run ./internal/filtering/blocked_test.go
|
||||
// 2. Use the output to replace `SERVICES` array in "client/src/helpers/constants.js".
|
||||
// 3. You'll need to enter services names manually.
|
||||
// 4. Don't forget to add missing icons to "client/src/components/ui/Icons.js".
|
||||
//
|
||||
// TODO(ameshkov): Rework generator: have a JSON file with all the metadata we need
|
||||
// then use this JSON file to generate JS and Go code
|
||||
func TestGenServicesArray(t *testing.T) {
|
||||
services := make([]svc, len(serviceRulesArray))
|
||||
copy(services, serviceRulesArray)
|
||||
|
||||
sort.Slice(services, func(i, j int) bool {
|
||||
return services[i].name < services[j].name
|
||||
})
|
||||
|
||||
fmt.Println("export const SERVICES = [")
|
||||
for _, s := range services {
|
||||
fmt.Printf(" {\n id: '%s',\n name: '%s',\n },\n", s.name, s.name)
|
||||
}
|
||||
fmt.Println("];")
|
||||
}
|
||||
@@ -420,11 +420,11 @@ type ResultRule struct {
|
||||
|
||||
// Result contains the result of a request check.
|
||||
//
|
||||
// All fields transitively have omitempty tags so that the query log
|
||||
// doesn't become too large.
|
||||
// All fields transitively have omitempty tags so that the query log doesn't
|
||||
// become too large.
|
||||
//
|
||||
// TODO(a.garipov): Clarify relationships between fields. Perhaps
|
||||
// replace with a sum type or an interface?
|
||||
// TODO(a.garipov): Clarify relationships between fields. Perhaps replace with
|
||||
// a sum type or an interface?
|
||||
type Result struct {
|
||||
// DNSRewriteResult is the $dnsrewrite filter rule result.
|
||||
DNSRewriteResult *DNSRewriteResult `json:",omitempty"`
|
||||
@@ -813,17 +813,18 @@ func (d *DNSFilter) matchHostProcessDNSResult(
|
||||
return res
|
||||
}
|
||||
|
||||
if dnsres.HostRulesV4 != nil || dnsres.HostRulesV6 != nil {
|
||||
// Question type doesn't match the host rules. Return the first matched
|
||||
// host rule, but without an IP address.
|
||||
var matchedRules []rules.Rule
|
||||
if dnsres.HostRulesV4 != nil {
|
||||
matchedRules = []rules.Rule{dnsres.HostRulesV4[0]}
|
||||
} else if dnsres.HostRulesV6 != nil {
|
||||
matchedRules = []rules.Rule{dnsres.HostRulesV6[0]}
|
||||
}
|
||||
return hostResultForOtherQType(dnsres)
|
||||
}
|
||||
|
||||
return makeResult(matchedRules, FilteredBlockList)
|
||||
// hostResultForOtherQType returns a result based on the host rules in dnsres,
|
||||
// if any. dnsres.HostRulesV4 take precedence over dnsres.HostRulesV6.
|
||||
func hostResultForOtherQType(dnsres *urlfilter.DNSResult) (res Result) {
|
||||
if len(dnsres.HostRulesV4) != 0 {
|
||||
return makeResult([]rules.Rule{dnsres.HostRulesV4[0]}, FilteredBlockList)
|
||||
}
|
||||
|
||||
if len(dnsres.HostRulesV6) != 0 {
|
||||
return makeResult([]rules.Rule{dnsres.HostRulesV6[0]}, FilteredBlockList)
|
||||
}
|
||||
|
||||
return Result{}
|
||||
@@ -840,7 +841,7 @@ func (d *DNSFilter) matchHost(
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
ureq := &urlfilter.DNSRequest{
|
||||
ufReq := &urlfilter.DNSRequest{
|
||||
Hostname: host,
|
||||
SortedClientTags: setts.ClientTags,
|
||||
// TODO(e.burkov): Wait for urlfilter update to pass net.IP.
|
||||
@@ -857,7 +858,7 @@ func (d *DNSFilter) matchHost(
|
||||
defer d.engineLock.RUnlock()
|
||||
|
||||
if setts.ProtectionEnabled && d.filteringEngineAllow != nil {
|
||||
dnsres, ok := d.filteringEngineAllow.MatchRequest(ureq)
|
||||
dnsres, ok := d.filteringEngineAllow.MatchRequest(ufReq)
|
||||
if ok {
|
||||
return d.matchHostProcessAllowList(host, dnsres)
|
||||
}
|
||||
@@ -867,17 +868,13 @@ func (d *DNSFilter) matchHost(
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
dnsres, ok := d.filteringEngine.MatchRequest(ureq)
|
||||
dnsres, matchedEngine := d.filteringEngine.MatchRequest(ufReq)
|
||||
|
||||
// Check DNS rewrites first, because the API there is a bit awkward.
|
||||
if dnsr := dnsres.DNSRewrites(); len(dnsr) > 0 {
|
||||
res = d.processDNSRewrites(dnsr)
|
||||
if res.Reason == RewrittenRule && res.CanonName == host {
|
||||
// A rewrite of a host to itself. Go on and try matching other
|
||||
// things.
|
||||
} else {
|
||||
return res, nil
|
||||
}
|
||||
} else if !ok {
|
||||
dnsRWRes := d.processDNSResultRewrites(dnsres, host)
|
||||
if dnsRWRes.Reason != NotFilteredNotFound {
|
||||
return dnsRWRes, nil
|
||||
} else if !matchedEngine {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
@@ -899,6 +896,26 @@ func (d *DNSFilter) matchHost(
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// processDNSResultRewrites returns an empty Result if there are no dnsrewrite
|
||||
// rules in dnsres. Otherwise, it returns the processed Result.
|
||||
func (d *DNSFilter) processDNSResultRewrites(
|
||||
dnsres *urlfilter.DNSResult,
|
||||
host string,
|
||||
) (dnsRWRes Result) {
|
||||
dnsr := dnsres.DNSRewrites()
|
||||
if len(dnsr) == 0 {
|
||||
return Result{}
|
||||
}
|
||||
|
||||
res := d.processDNSRewrites(dnsr)
|
||||
if res.Reason == RewrittenRule && res.CanonName == host {
|
||||
// A rewrite of a host to itself. Go on and try matching other things.
|
||||
return Result{}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// makeResult returns a properly constructed Result.
|
||||
func makeResult(matchedRules []rules.Rule, reason Reason) (res Result) {
|
||||
resRules := make([]*ResultRule, len(matchedRules))
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// DNS Rewrites
|
||||
|
||||
package filtering
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
@@ -14,6 +11,8 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Legacy DNS rewrites
|
||||
|
||||
// LegacyRewrite is a single legacy DNS rewrite record.
|
||||
//
|
||||
// Instances of *LegacyRewrite must never be nil.
|
||||
@@ -123,38 +122,24 @@ func matchDomainWildcard(host, wildcard string) (ok bool) {
|
||||
return isWildcard(wildcard) && strings.HasSuffix(host, wildcard[1:])
|
||||
}
|
||||
|
||||
// rewritesSorted is a slice of legacy rewrites for sorting.
|
||||
// legacyRewriteSortsBefore sorts rewirtes according to the following priority:
|
||||
//
|
||||
// The sorting priority:
|
||||
//
|
||||
// 1. A and AAAA > CNAME
|
||||
// 2. wildcard > exact
|
||||
// 3. lower level wildcard > higher level wildcard
|
||||
//
|
||||
// TODO(a.garipov): Replace with slices.Sort.
|
||||
type rewritesSorted []*LegacyRewrite
|
||||
|
||||
// Len implements the sort.Interface interface for rewritesSorted.
|
||||
func (a rewritesSorted) Len() (l int) { return len(a) }
|
||||
|
||||
// Swap implements the sort.Interface interface for rewritesSorted.
|
||||
func (a rewritesSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// Less implements the sort.Interface interface for rewritesSorted.
|
||||
func (a rewritesSorted) Less(i, j int) (less bool) {
|
||||
ith, jth := a[i], a[j]
|
||||
if ith.Type == dns.TypeCNAME && jth.Type != dns.TypeCNAME {
|
||||
// 1. A and AAAA > CNAME;
|
||||
// 2. wildcard > exact;
|
||||
// 3. lower level wildcard > higher level wildcard;
|
||||
func legacyRewriteSortsBefore(a, b *LegacyRewrite) (sortsBefore bool) {
|
||||
if a.Type == dns.TypeCNAME && b.Type != dns.TypeCNAME {
|
||||
return true
|
||||
} else if ith.Type != dns.TypeCNAME && jth.Type == dns.TypeCNAME {
|
||||
} else if a.Type != dns.TypeCNAME && b.Type == dns.TypeCNAME {
|
||||
return false
|
||||
}
|
||||
|
||||
if iw, jw := isWildcard(ith.Domain), isWildcard(jth.Domain); iw != jw {
|
||||
return jw
|
||||
if aIsWld, bIsWld := isWildcard(a.Domain), isWildcard(b.Domain); aIsWld != bIsWld {
|
||||
return bIsWld
|
||||
}
|
||||
|
||||
// Both are either wildcards or not.
|
||||
return len(ith.Domain) > len(jth.Domain)
|
||||
// Both are either wildcards or both aren't.
|
||||
return len(a.Domain) > len(b.Domain)
|
||||
}
|
||||
|
||||
// prepareRewrites normalizes and validates all legacy DNS rewrites.
|
||||
@@ -196,7 +181,7 @@ func findRewrites(
|
||||
return nil, matched
|
||||
}
|
||||
|
||||
sort.Sort(rewritesSorted(rewrites))
|
||||
slices.SortFunc(rewrites, legacyRewriteSortsBefore)
|
||||
|
||||
for i, r := range rewrites {
|
||||
if isWildcard(r.Domain) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -19,6 +18,7 @@ import (
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
@@ -241,8 +241,8 @@ func (c *sbCtx) processTXT(resp *dns.Msg) (bool, [][]byte) {
|
||||
}
|
||||
|
||||
func (c *sbCtx) storeCache(hashes [][]byte) {
|
||||
sort.Slice(hashes, func(a, b int) bool {
|
||||
return bytes.Compare(hashes[a], hashes[b]) == -1
|
||||
slices.SortFunc(hashes, func(a, b []byte) (sortsBefore bool) {
|
||||
return bytes.Compare(a, b) == -1
|
||||
})
|
||||
|
||||
var curData []byte
|
||||
|
||||
@@ -13,8 +13,37 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
)
|
||||
|
||||
// SafeSearch interface describes a service for search engines hosts rewrites.
|
||||
type SafeSearch interface {
|
||||
// SearchHost returns a replacement address for the search engine host.
|
||||
SearchHost(host string, qtype uint16) (res *rules.DNSRewrite)
|
||||
|
||||
// CheckHost checks host with safe search engine.
|
||||
CheckHost(host string, qtype uint16) (res Result, err error)
|
||||
}
|
||||
|
||||
// SafeSearchConfig is a struct with safe search related settings.
|
||||
type SafeSearchConfig struct {
|
||||
// CustomResolver is the resolver used by safe search.
|
||||
CustomResolver Resolver `yaml:"-"`
|
||||
|
||||
// Enabled indicates if safe search is enabled entirely.
|
||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||
|
||||
// Services flags. Each flag indicates if the corresponding service is
|
||||
// enabled or disabled.
|
||||
|
||||
Bing bool `yaml:"bing" json:"bing"`
|
||||
DuckDuckGo bool `yaml:"duckduckgo" json:"duckduckgo"`
|
||||
Google bool `yaml:"google" json:"google"`
|
||||
Pixabay bool `yaml:"pixabay" json:"pixabay"`
|
||||
Yandex bool `yaml:"yandex" json:"yandex"`
|
||||
YouTube bool `yaml:"youtube" json:"youtube"`
|
||||
}
|
||||
|
||||
/*
|
||||
expire byte[4]
|
||||
res Result
|
||||
|
||||
34
internal/filtering/safesearch/rules.go
Normal file
34
internal/filtering/safesearch/rules.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package safesearch
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed rules/bing.txt
|
||||
var bing string
|
||||
|
||||
//go:embed rules/google.txt
|
||||
var google string
|
||||
|
||||
//go:embed rules/pixabay.txt
|
||||
var pixabay string
|
||||
|
||||
//go:embed rules/duckduckgo.txt
|
||||
var duckduckgo string
|
||||
|
||||
//go:embed rules/yandex.txt
|
||||
var yandex string
|
||||
|
||||
//go:embed rules/youtube.txt
|
||||
var youtube string
|
||||
|
||||
// safeSearchRules is a map with rules texts grouped by search providers.
|
||||
// Source rules downloaded from:
|
||||
// https://adguardteam.github.io/HostlistsRegistry/assets/engines_safe_search.txt,
|
||||
// https://adguardteam.github.io/HostlistsRegistry/assets/youtube_safe_search.txt.
|
||||
var safeSearchRules = map[Service]string{
|
||||
Bing: bing,
|
||||
DuckDuckGo: duckduckgo,
|
||||
Google: google,
|
||||
Pixabay: pixabay,
|
||||
Yandex: yandex,
|
||||
YouTube: youtube,
|
||||
}
|
||||
1
internal/filtering/safesearch/rules/bing.txt
Normal file
1
internal/filtering/safesearch/rules/bing.txt
Normal file
@@ -0,0 +1 @@
|
||||
|www.bing.com^$dnsrewrite=NOERROR;CNAME;strict.bing.com
|
||||
3
internal/filtering/safesearch/rules/duckduckgo.txt
Normal file
3
internal/filtering/safesearch/rules/duckduckgo.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
|duckduckgo.com^$dnsrewrite=NOERROR;CNAME;safe.duckduckgo.com
|
||||
|start.duckduckgo.com^$dnsrewrite=NOERROR;CNAME;safe.duckduckgo.com
|
||||
|www.duckduckgo.com^$dnsrewrite=NOERROR;CNAME;safe.duckduckgo.com
|
||||
191
internal/filtering/safesearch/rules/google.txt
Normal file
191
internal/filtering/safesearch/rules/google.txt
Normal file
@@ -0,0 +1,191 @@
|
||||
|www.google.ad^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ae^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.al^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.am^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.as^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.at^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.az^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ba^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.be^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.bf^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.bg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.bi^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.bj^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.bs^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.bt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.by^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ca^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.cat^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.cd^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.cf^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.cg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ch^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ci^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.cl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.cm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.cn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.ao^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.bw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.ck^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.cr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.id^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.il^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.in^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.jp^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.ke^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.kr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.ls^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.ma^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.mz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.nz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.th^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.tz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.ug^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.uk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.uz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.ve^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.co.vi^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.af^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.ag^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.ai^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.ar^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.au^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.bd^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.bh^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.bn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.bo^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.br^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.bz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.co^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.cu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.cy^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.do^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.ec^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.eg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.et^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.fj^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.gh^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.gi^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.gt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.hk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.jm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.kh^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.kw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.lb^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.ly^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.mm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.mt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.mx^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.my^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.na^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.nf^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.ng^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.ni^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.np^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.om^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.pa^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.pe^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.pg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.ph^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.pk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.pr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.py^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.qa^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.sa^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.sb^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.sg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.sl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.sv^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.tj^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.tr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.tw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.ua^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.uy^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.vc^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com.vn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.com^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.cv^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.cz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.de^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.dj^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.dk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.dm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.dz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ee^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.es^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.fi^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.fm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.fr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ga^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ge^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.gg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.gl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.gm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.gp^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.gr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.gy^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.hn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.hr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ht^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.hu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ie^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.im^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.iq^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.is^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.it^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.je^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.jo^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.kg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ki^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.kz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.la^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.li^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.lk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.lt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.lu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.lv^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.md^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.me^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.mg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.mk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ml^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.mn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ms^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.mu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.mv^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.mw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ne^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.nl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.no^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.nr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.nu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.pl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.pn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ps^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.pt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ro^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.rs^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ru^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.rw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.sc^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.se^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.sh^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.si^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.sk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.sm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.sn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.so^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.sr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.st^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.td^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.tg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.tk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.tl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.tm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.tn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.to^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.tt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.vg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.vu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
|www.google.ws^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com
|
||||
1
internal/filtering/safesearch/rules/pixabay.txt
Normal file
1
internal/filtering/safesearch/rules/pixabay.txt
Normal file
@@ -0,0 +1 @@
|
||||
|pixabay.com^$dnsrewrite=NOERROR;CNAME;safesearch.pixabay.com
|
||||
52
internal/filtering/safesearch/rules/yandex.txt
Normal file
52
internal/filtering/safesearch/rules/yandex.txt
Normal file
@@ -0,0 +1,52 @@
|
||||
|www.xn--d1acpjx3f.xn--p1ai^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.ya.ru^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.az^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.by^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.co.il^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.com.am^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.com.ge^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.com.ru^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.com.tr^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.com^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.de^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.ee^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.eu^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.fi^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.fr^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.kz^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.lt^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.lv^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.md^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.net^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.org^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.pl^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.ru^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.tj^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.tm^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|www.yandex.uz^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|xn--d1acpjx3f.xn--p1ai^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|ya.ru^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.az^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.by^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.co.il^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.com.am^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.com.ge^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.com.ru^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.com.tr^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.com^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.de^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.ee^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.eu^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.fi^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.fr^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.kz^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.lt^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.lv^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.md^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.net^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.org^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.pl^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.ru^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.tj^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.tm^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
|yandex.uz^$dnsrewrite=NOERROR;A;213.180.193.56
|
||||
5
internal/filtering/safesearch/rules/youtube.txt
Normal file
5
internal/filtering/safesearch/rules/youtube.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
|www.youtube.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com
|
||||
|m.youtube.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com
|
||||
|youtubei.googleapis.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com
|
||||
|youtube.googleapis.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com
|
||||
|www.youtube-nocookie.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com
|
||||
269
internal/filtering/safesearch/safesearch.go
Normal file
269
internal/filtering/safesearch/safesearch.go
Normal file
@@ -0,0 +1,269 @@
|
||||
// Package safesearch implements safesearch host matching.
|
||||
package safesearch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Service is a enum with service names used as search providers.
|
||||
type Service string
|
||||
|
||||
// Service enum members.
|
||||
const (
|
||||
Bing Service = "bing"
|
||||
DuckDuckGo Service = "duckduckgo"
|
||||
Google Service = "google"
|
||||
Pixabay Service = "pixabay"
|
||||
Yandex Service = "yandex"
|
||||
YouTube Service = "youtube"
|
||||
)
|
||||
|
||||
// isServiceProtected returns true if the service safe search is active.
|
||||
func isServiceProtected(s filtering.SafeSearchConfig, service Service) (ok bool) {
|
||||
switch service {
|
||||
case Bing:
|
||||
return s.Bing
|
||||
case DuckDuckGo:
|
||||
return s.DuckDuckGo
|
||||
case Google:
|
||||
return s.Google
|
||||
case Pixabay:
|
||||
return s.Pixabay
|
||||
case Yandex:
|
||||
return s.Yandex
|
||||
case YouTube:
|
||||
return s.YouTube
|
||||
default:
|
||||
panic(fmt.Errorf("safesearch: invalid sources: not found service %q", service))
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultSafeSearch is the default safesearch struct.
|
||||
type DefaultSafeSearch struct {
|
||||
engine *urlfilter.DNSEngine
|
||||
safeSearchCache cache.Cache
|
||||
resolver filtering.Resolver
|
||||
cacheTime time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultSafeSearch returns new safesearch struct. CacheTime is an element
|
||||
// TTL (in minutes).
|
||||
func NewDefaultSafeSearch(
|
||||
conf filtering.SafeSearchConfig,
|
||||
cacheSize uint,
|
||||
cacheTime time.Duration,
|
||||
) (ss *DefaultSafeSearch, err error) {
|
||||
engine, err := newEngine(filtering.SafeSearchListID, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resolver filtering.Resolver = net.DefaultResolver
|
||||
if conf.CustomResolver != nil {
|
||||
resolver = conf.CustomResolver
|
||||
}
|
||||
|
||||
return &DefaultSafeSearch{
|
||||
engine: engine,
|
||||
safeSearchCache: cache.New(cache.Config{
|
||||
EnableLRU: true,
|
||||
MaxSize: cacheSize,
|
||||
}),
|
||||
cacheTime: cacheTime,
|
||||
resolver: resolver,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newEngine creates new engine for provided safe search configuration.
|
||||
func newEngine(listID int, conf filtering.SafeSearchConfig) (engine *urlfilter.DNSEngine, err error) {
|
||||
var sb strings.Builder
|
||||
for service, serviceRules := range safeSearchRules {
|
||||
if isServiceProtected(conf, service) {
|
||||
sb.WriteString(serviceRules)
|
||||
}
|
||||
}
|
||||
|
||||
strList := &filterlist.StringRuleList{
|
||||
ID: listID,
|
||||
RulesText: sb.String(),
|
||||
IgnoreCosmetic: true,
|
||||
}
|
||||
|
||||
rs, err := filterlist.NewRuleStorage([]filterlist.RuleList{strList})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating rule storage: %w", err)
|
||||
}
|
||||
|
||||
engine = urlfilter.NewDNSEngine(rs)
|
||||
log.Info("safesearch: filter %d: reset %d rules", listID, engine.RulesCount)
|
||||
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ filtering.SafeSearch = (*DefaultSafeSearch)(nil)
|
||||
|
||||
// SearchHost implements the [filtering.SafeSearch] interface for *DefaultSafeSearch.
|
||||
func (ss *DefaultSafeSearch) SearchHost(host string, qtype uint16) (res *rules.DNSRewrite) {
|
||||
r, _ := ss.engine.MatchRequest(&urlfilter.DNSRequest{
|
||||
Hostname: strings.ToLower(host),
|
||||
DNSType: qtype,
|
||||
})
|
||||
|
||||
rewritesRules := r.DNSRewrites()
|
||||
if len(rewritesRules) > 0 {
|
||||
return rewritesRules[0].DNSRewrite
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckHost implements the [filtering.SafeSearch] interface for
|
||||
// *DefaultSafeSearch.
|
||||
func (ss *DefaultSafeSearch) CheckHost(
|
||||
host string,
|
||||
qtype uint16,
|
||||
) (res filtering.Result, err error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("safesearch: lookup for %s", host)
|
||||
}
|
||||
|
||||
// Check cache. Return cached result if it was found
|
||||
cachedValue, isFound := ss.getCachedResult(host)
|
||||
if isFound {
|
||||
log.Debug("safesearch: found in cache: %s", host)
|
||||
|
||||
return cachedValue, nil
|
||||
}
|
||||
|
||||
rewrite := ss.SearchHost(host, qtype)
|
||||
if rewrite == nil {
|
||||
return filtering.Result{}, nil
|
||||
}
|
||||
|
||||
dRes, err := ss.newResult(rewrite, qtype)
|
||||
if err != nil {
|
||||
log.Debug("safesearch: failed to lookup addresses for %s: %s", host, err)
|
||||
|
||||
return filtering.Result{}, err
|
||||
}
|
||||
|
||||
if dRes != nil {
|
||||
res = *dRes
|
||||
ss.setCacheResult(host, res)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return filtering.Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", host)
|
||||
}
|
||||
|
||||
// newResult creates Result object from rewrite rule.
|
||||
func (ss *DefaultSafeSearch) newResult(
|
||||
rewrite *rules.DNSRewrite,
|
||||
qtype uint16,
|
||||
) (res *filtering.Result, err error) {
|
||||
res = &filtering.Result{
|
||||
Rules: []*filtering.ResultRule{{
|
||||
FilterListID: filtering.SafeSearchListID,
|
||||
}},
|
||||
Reason: filtering.FilteredSafeSearch,
|
||||
IsFiltered: true,
|
||||
}
|
||||
|
||||
if rewrite.RRType == qtype && (qtype == dns.TypeA || qtype == dns.TypeAAAA) {
|
||||
ip, ok := rewrite.Value.(net.IP)
|
||||
if !ok || ip == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res.Rules[0].IP = ip
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if rewrite.NewCNAME == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ips, err := ss.resolver.LookupIP(context.Background(), "ip", rewrite.NewCNAME)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
if ip = ip.To4(); ip == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
res.Rules[0].IP = ip
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// setCacheResult stores data in cache for host.
|
||||
func (ss *DefaultSafeSearch) setCacheResult(host string, res filtering.Result) {
|
||||
expire := uint32(time.Now().Add(ss.cacheTime).Unix())
|
||||
exp := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(exp, expire)
|
||||
buf := bytes.NewBuffer(exp)
|
||||
|
||||
err := gob.NewEncoder(buf).Encode(res)
|
||||
if err != nil {
|
||||
log.Error("safesearch: cache encoding: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
val := buf.Bytes()
|
||||
_ = ss.safeSearchCache.Set([]byte(host), val)
|
||||
|
||||
log.Debug("safesearch: stored in cache: %s (%d bytes)", host, len(val))
|
||||
}
|
||||
|
||||
// getCachedResult returns stored data from cache for host.
|
||||
func (ss *DefaultSafeSearch) getCachedResult(host string) (res filtering.Result, ok bool) {
|
||||
res = filtering.Result{}
|
||||
|
||||
data := ss.safeSearchCache.Get([]byte(host))
|
||||
if data == nil {
|
||||
return res, false
|
||||
}
|
||||
|
||||
exp := binary.BigEndian.Uint32(data[:4])
|
||||
if exp <= uint32(time.Now().Unix()) {
|
||||
ss.safeSearchCache.Del([]byte(host))
|
||||
|
||||
return res, false
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(data[4:])
|
||||
|
||||
err := gob.NewDecoder(buf).Decode(&res)
|
||||
if err != nil {
|
||||
log.Debug("safesearch: cache decoding: %s", err)
|
||||
|
||||
return filtering.Result{}, false
|
||||
}
|
||||
|
||||
return res, true
|
||||
}
|
||||
202
internal/filtering/safesearch/safesearch_test.go
Normal file
202
internal/filtering/safesearch/safesearch_test.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package safesearch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
safeSearchCacheSize = 5000
|
||||
cacheTime = 30 * time.Minute
|
||||
)
|
||||
|
||||
var defaultSafeSearchConf = filtering.SafeSearchConfig{
|
||||
Enabled: true,
|
||||
Bing: true,
|
||||
DuckDuckGo: true,
|
||||
Google: true,
|
||||
Pixabay: true,
|
||||
Yandex: true,
|
||||
YouTube: true,
|
||||
}
|
||||
|
||||
var yandexIP = net.IPv4(213, 180, 193, 56)
|
||||
|
||||
func newForTest(t testing.TB, ssConf filtering.SafeSearchConfig) (ss *DefaultSafeSearch) {
|
||||
ss, err := NewDefaultSafeSearch(ssConf, safeSearchCacheSize, cacheTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
func TestSafeSearch(t *testing.T) {
|
||||
ss := newForTest(t, defaultSafeSearchConf)
|
||||
val := ss.SearchHost("www.google.com", dns.TypeA)
|
||||
|
||||
assert.Equal(t, &rules.DNSRewrite{NewCNAME: "forcesafesearch.google.com"}, val)
|
||||
}
|
||||
|
||||
func TestCheckHostSafeSearchYandex(t *testing.T) {
|
||||
ss := newForTest(t, defaultSafeSearchConf)
|
||||
|
||||
// Check host for each domain.
|
||||
for _, host := range []string{
|
||||
"yandex.ru",
|
||||
"yAndeX.ru",
|
||||
"YANdex.COM",
|
||||
"yandex.by",
|
||||
"yandex.kz",
|
||||
"www.yandex.com",
|
||||
} {
|
||||
res, err := ss.CheckHost(host, dns.TypeA)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, res.IsFiltered)
|
||||
|
||||
require.Len(t, res.Rules, 1)
|
||||
|
||||
assert.Equal(t, yandexIP, res.Rules[0].IP)
|
||||
assert.EqualValues(t, filtering.SafeSearchListID, res.Rules[0].FilterListID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckHostSafeSearchGoogle(t *testing.T) {
|
||||
resolver := &aghtest.TestResolver{}
|
||||
ip, _ := resolver.HostToIPs("forcesafesearch.google.com")
|
||||
|
||||
ss := newForTest(t, defaultSafeSearchConf)
|
||||
ss.resolver = resolver
|
||||
|
||||
// Check host for each domain.
|
||||
for _, host := range []string{
|
||||
"www.google.com",
|
||||
"www.google.im",
|
||||
"www.google.co.in",
|
||||
"www.google.iq",
|
||||
"www.google.is",
|
||||
"www.google.it",
|
||||
"www.google.je",
|
||||
} {
|
||||
t.Run(host, func(t *testing.T) {
|
||||
res, err := ss.CheckHost(host, dns.TypeA)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, res.IsFiltered)
|
||||
|
||||
require.Len(t, res.Rules, 1)
|
||||
|
||||
assert.Equal(t, ip, res.Rules[0].IP)
|
||||
assert.EqualValues(t, filtering.SafeSearchListID, res.Rules[0].FilterListID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeSearchCacheYandex(t *testing.T) {
|
||||
const domain = "yandex.ru"
|
||||
|
||||
ss := newForTest(t, filtering.SafeSearchConfig{Enabled: false})
|
||||
|
||||
// Check host with disabled safesearch.
|
||||
res, err := ss.CheckHost(domain, dns.TypeA)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, res.IsFiltered)
|
||||
assert.Empty(t, res.Rules)
|
||||
|
||||
ss = newForTest(t, defaultSafeSearchConf)
|
||||
res, err = ss.CheckHost(domain, dns.TypeA)
|
||||
require.NoError(t, err)
|
||||
|
||||
// For yandex we already know valid IP.
|
||||
require.Len(t, res.Rules, 1)
|
||||
|
||||
assert.Equal(t, res.Rules[0].IP, yandexIP)
|
||||
|
||||
// Check cache.
|
||||
cachedValue, isFound := ss.getCachedResult(domain)
|
||||
require.True(t, isFound)
|
||||
require.Len(t, cachedValue.Rules, 1)
|
||||
|
||||
assert.Equal(t, cachedValue.Rules[0].IP, yandexIP)
|
||||
}
|
||||
|
||||
func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||
const domain = "www.google.ru"
|
||||
|
||||
ss := newForTest(t, filtering.SafeSearchConfig{Enabled: false})
|
||||
|
||||
res, err := ss.CheckHost(domain, dns.TypeA)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, res.IsFiltered)
|
||||
assert.Empty(t, res.Rules)
|
||||
|
||||
resolver := &aghtest.TestResolver{}
|
||||
ss = newForTest(t, defaultSafeSearchConf)
|
||||
ss.resolver = resolver
|
||||
|
||||
// Lookup for safesearch domain.
|
||||
rewrite := ss.SearchHost(domain, dns.TypeA)
|
||||
|
||||
ips, err := resolver.LookupIP(context.Background(), "ip", rewrite.NewCNAME)
|
||||
require.NoError(t, err)
|
||||
|
||||
var foundIP net.IP
|
||||
for _, ip := range ips {
|
||||
if ip.To4() != nil {
|
||||
foundIP = ip
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
res, err = ss.CheckHost(domain, dns.TypeA)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res.Rules, 1)
|
||||
|
||||
assert.True(t, res.Rules[0].IP.Equal(foundIP))
|
||||
|
||||
// Check cache.
|
||||
cachedValue, isFound := ss.getCachedResult(domain)
|
||||
require.True(t, isFound)
|
||||
require.Len(t, cachedValue.Rules, 1)
|
||||
|
||||
assert.True(t, cachedValue.Rules[0].IP.Equal(foundIP))
|
||||
}
|
||||
|
||||
const googleHost = "www.google.com"
|
||||
|
||||
var dnsRewriteSink *rules.DNSRewrite
|
||||
|
||||
func BenchmarkSafeSearch(b *testing.B) {
|
||||
ss := newForTest(b, defaultSafeSearchConf)
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
dnsRewriteSink = ss.SearchHost(googleHost, dns.TypeA)
|
||||
}
|
||||
|
||||
assert.Equal(b, "forcesafesearch.google.com", dnsRewriteSink.NewCNAME)
|
||||
}
|
||||
|
||||
var dnsRewriteParallelSink *rules.DNSRewrite
|
||||
|
||||
func BenchmarkSafeSearch_parallel(b *testing.B) {
|
||||
ss := newForTest(b, defaultSafeSearchConf)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
dnsRewriteParallelSink = ss.SearchHost(googleHost, dns.TypeA)
|
||||
}
|
||||
})
|
||||
|
||||
assert.Equal(b, "forcesafesearch.google.com", dnsRewriteParallelSink.NewCNAME)
|
||||
}
|
||||
@@ -71,6 +71,7 @@ var blockedServices = []blockedService{{
|
||||
"||amazon.jp^",
|
||||
"||amazon.nl^",
|
||||
"||amazon.red^",
|
||||
"||amazon.se^",
|
||||
"||amazon.sg^",
|
||||
"||amazon^",
|
||||
"||amazonalexavoxcon.com^",
|
||||
@@ -1171,6 +1172,16 @@ var blockedServices = []blockedService{{
|
||||
"||zuckerberg.com^",
|
||||
"||zuckerberg.net^",
|
||||
},
|
||||
}, {
|
||||
ID: "gog",
|
||||
Name: "GOG",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 34 34\"><path d=\"M31 31H3a3 3 0 0 1-3-3V3A3 3 0 0 1 3 0H31a3 3 0 0 1 3 3V28A3 3 0 0 1 31 31ZM4 24.5A1.5 1.5 0 0 0 5.5 26H11V24H6.5a.5.5 0 0 1-.5-.5v-3a.5.5 0 0 1 .5-.5H11V18H5.5A1.5 1.5 0 0 0 4 19.5Zm8-18A1.5 1.5 0 0 0 10.5 5h-5A1.5 1.5 0 0 0 4 6.5v5A1.5 1.5 0 0 0 5.5 13H9V11H6.5a.5.5 0 0 1-.5-.5v-3A.5.5 0 0 1 6.5 7h3a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-.5.5H4v2h6.5A1.5 1.5 0 0 0 12 14.5Zm0 13v5A1.5 1.5 0 0 0 13.5 26h5A1.5 1.5 0 0 0 20 24.5v-5A1.5 1.5 0 0 0 18.5 18h-5A1.5 1.5 0 0 0 12 19.5Zm9-13A1.5 1.5 0 0 0 19.5 5h-5A1.5 1.5 0 0 0 13 6.5v5A1.5 1.5 0 0 0 14.5 13h5A1.5 1.5 0 0 0 21 11.5Zm9 0A1.5 1.5 0 0 0 28.5 5h-5A1.5 1.5 0 0 0 22 6.5v5A1.5 1.5 0 0 0 23.5 13H27V11H24.5a.5.5 0 0 1-.5-.5v-3a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-.5.5H22v2h6.5A1.5 1.5 0 0 0 30 14.5ZM30 18H22.5A1.5 1.5 0 0 0 21 19.5V26h2V20.5a.5.5 0 0 1 .5-.5h1v6h2V20H28v6h2ZM18.5 11h-3a.5.5 0 0 1-.5-.5v-3a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5v3A.5.5 0 0 1 18.5 11Zm-4 9h3a.5.5 0 0 1 .5.5v3a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-3A.5.5 0 0 1 14.5 20Z\" /></svg>"),
|
||||
Rules: []string{
|
||||
"||gog-cdn-lumen.secure2.footprint.net^",
|
||||
"||gog-statics.com^",
|
||||
"||gog.com^",
|
||||
"||gogalaxy.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "hulu",
|
||||
Name: "Hulu",
|
||||
@@ -1280,6 +1291,14 @@ var blockedServices = []blockedService{{
|
||||
"||iq.com^",
|
||||
"||iqiyi.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "kakaotalk",
|
||||
Name: "KakaoTalk",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\" ><path d=\"M22.125 0H1.875C.839 0 0 .84 0 1.875v20.25C0 23.161.84 24 1.875 24h20.25C23.161 24 24 23.16 24 22.125V1.875C24 .839 23.16 0 22.125 0zM12 18.75c-.591 0-1.17-.041-1.732-.12-.562.396-3.813 2.679-4.12 2.722 0 0-.125.049-.232-.014s-.088-.229-.088-.229c.032-.22.843-3.018.992-3.533-2.745-1.36-4.57-3.769-4.57-6.513 0-4.246 4.365-7.688 9.75-7.688s9.75 3.442 9.75 7.688c0 4.245-4.365 7.687-9.75 7.687zM8.05 9.867h-.878v3.342c0 .296-.252.537-.563.537s-.562-.24-.562-.537V9.867h-.878a.552.552 0 0 1 0-1.101h2.88a.552.552 0 0 1 0 1.101zm10.987 2.957a.558.558 0 0 1 .109.417.559.559 0 0 1-.219.37.557.557 0 0 1-.338.114.558.558 0 0 1-.45-.224l-1.319-1.747-.195.195v1.227a.564.564 0 0 1-.562.563.563.563 0 0 1-.563-.563V9.328a.563.563 0 0 1 1.125 0v1.21l1.57-1.57a.437.437 0 0 1 .311-.126c.14 0 .282.061.388.167a.555.555 0 0 1 .165.356.438.438 0 0 1-.124.343l-1.282 1.281 1.385 1.835zm-8.35-3.502c-.095-.27-.383-.548-.75-.556-.366.008-.654.286-.749.555l-1.345 3.541c-.171.53-.022.728.133.8a.857.857 0 0 0 .357.077c.235 0 .414-.095.468-.248l.279-.73h1.715l.279.73c.054.153.233.248.468.248a.86.86 0 0 0 .357-.078c.155-.071.304-.268.133-.8l-1.345-3.54zm-1.311 2.443.562-1.596.561 1.596H9.376zm5.905 1.383a.528.528 0 0 1-.539.516h-1.804a.528.528 0 0 1-.54-.516v-3.82c0-.31.258-.562.575-.562s.574.252.574.562v3.305h1.195c.297 0 .54.231.54.515z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||kakao.com^",
|
||||
"||kgslb.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "leagueoflegends",
|
||||
Name: "League of Legends",
|
||||
@@ -1291,6 +1310,24 @@ var blockedServices = []blockedService{{
|
||||
"||lolstatic.com^",
|
||||
"||lolusercontent.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "line",
|
||||
Name: "LINE",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 9 4 C 6.24 4 4 6.24 4 9 L 4 41 C 4 43.76 6.24 46 9 46 L 41 46 C 43.76 46 46 43.76 46 41 L 46 9 C 46 6.24 43.76 4 41 4 L 9 4 z M 25 11 C 33.27 11 40 16.359219 40 22.949219 C 40 25.579219 38.959297 27.960781 36.779297 30.300781 C 35.209297 32.080781 32.660547 34.040156 30.310547 35.660156 C 27.960547 37.260156 25.8 38.519609 25 38.849609 C 24.68 38.979609 24.44 39.039062 24.25 39.039062 C 23.59 39.039062 23.649219 38.340781 23.699219 38.050781 C 23.739219 37.830781 23.919922 36.789063 23.919922 36.789062 C 23.969922 36.419063 24.019141 35.830937 23.869141 35.460938 C 23.699141 35.050938 23.029062 34.840234 22.539062 34.740234 C 15.339063 33.800234 10 28.849219 10 22.949219 C 10 16.359219 16.73 11 25 11 z M 23.992188 18.998047 C 23.488379 19.007393 23 19.391875 23 20 L 23 26 C 23 26.552 23.448 27 24 27 C 24.552 27 25 26.552 25 26 L 25 23.121094 L 27.185547 26.580078 C 27.751547 27.372078 29 26.973 29 26 L 29 20 C 29 19.448 28.552 19 28 19 C 27.448 19 27 19.448 27 20 L 27 23 L 24.814453 19.419922 C 24.602203 19.122922 24.294473 18.992439 23.992188 18.998047 z M 15 19 C 14.448 19 14 19.448 14 20 L 14 26 C 14 26.552 14.448 27 15 27 L 18 27 C 18.552 27 19 26.552 19 26 C 19 25.448 18.552 25 18 25 L 16 25 L 16 20 C 16 19.448 15.552 19 15 19 z M 21 19 C 20.448 19 20 19.448 20 20 L 20 26 C 20 26.552 20.448 27 21 27 C 21.552 27 22 26.552 22 26 L 22 20 C 22 19.448 21.552 19 21 19 z M 31 19 C 30.448 19 30 19.448 30 20 L 30 26 C 30 26.552 30.448 27 31 27 L 34 27 C 34.552 27 35 26.552 35 26 C 35 25.448 34.552 25 34 25 L 32 25 L 32 24 L 34 24 C 34.553 24 35 23.552 35 23 C 35 22.448 34.553 22 34 22 L 32 22 L 32 21 L 34 21 C 34.552 21 35 20.552 35 20 C 35 19.448 34.552 19 34 19 L 31 19 z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||gcld-line.com^",
|
||||
"||lin.ee^",
|
||||
"||line-apps-beta.com^",
|
||||
"||line-apps-rc.com^",
|
||||
"||line-apps.com^",
|
||||
"||line-cdn.net^",
|
||||
"||line-scdn.net^",
|
||||
"||line.me^",
|
||||
"||line.naver.jp^",
|
||||
"||linecorp.com^",
|
||||
"||linemyshop.com^",
|
||||
"||lineshoppingseller.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "mail_ru",
|
||||
Name: "Mail.ru",
|
||||
@@ -1308,13 +1345,13 @@ var blockedServices = []blockedService{{
|
||||
"||aus.social^",
|
||||
"||awscommunity.social^",
|
||||
"||cyberplace.social^",
|
||||
"||defcon.social^",
|
||||
"||det.social^",
|
||||
"||fosstodon.org^",
|
||||
"||glasgow.social^",
|
||||
"||h4.io^",
|
||||
"||hachyderm.io^",
|
||||
"||hessen.social^",
|
||||
"||home.social^",
|
||||
"||hostux.social^",
|
||||
"||ieji.de^",
|
||||
"||indieweb.social^",
|
||||
@@ -1333,6 +1370,7 @@ var blockedServices = []blockedService{{
|
||||
"||mastodon.au^",
|
||||
"||mastodon.bida.im^",
|
||||
"||mastodon.com.tr^",
|
||||
"||mastodon.eus^",
|
||||
"||mastodon.green^",
|
||||
"||mastodon.ie^",
|
||||
"||mastodon.iriseden.eu^",
|
||||
@@ -1360,10 +1398,8 @@ var blockedServices = []blockedService{{
|
||||
"||mindly.social^",
|
||||
"||mstdn.ca^",
|
||||
"||mstdn.jp^",
|
||||
"||mstdn.party^",
|
||||
"||mstdn.social^",
|
||||
"||muenchen.social^",
|
||||
"||nerdculture.de^",
|
||||
"||newsie.social^",
|
||||
"||noc.social^",
|
||||
"||norden.social^",
|
||||
@@ -1382,6 +1418,7 @@ var blockedServices = []blockedService{{
|
||||
"||social.anoxinon.de^",
|
||||
"||social.cologne^",
|
||||
"||social.dev-wiki.de^",
|
||||
"||social.linux.pizza^",
|
||||
"||social.politicaconciencia.org^",
|
||||
"||social.vivaldi.net^",
|
||||
"||sself.co^",
|
||||
@@ -1398,11 +1435,11 @@ var blockedServices = []blockedService{{
|
||||
"||toot.wales^",
|
||||
"||troet.cafe^",
|
||||
"||twingyeo.kr^",
|
||||
"||uiuxdev.social^",
|
||||
"||union.place^",
|
||||
"||universeodon.com^",
|
||||
"||urbanists.social^",
|
||||
"||vocalodon.net^",
|
||||
"||wien.rocks^",
|
||||
"||wxw.moe^",
|
||||
},
|
||||
}, {
|
||||
@@ -1599,6 +1636,14 @@ var blockedServices = []blockedService{{
|
||||
"||snapchat.com^",
|
||||
"||snapkit.co",
|
||||
},
|
||||
}, {
|
||||
ID: "soundcloud",
|
||||
Name: "SoundCloud",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\" fill-rule=\"evenodd\" clip-rule=\"evenodd\"><path d=\"M19 17.75c2.07 0 3.75-1.68 3.75-3.75 0-2.07-1.68-3.75-3.75-3.75-.173 0-.344.012-.511.035-.73-2.337-2.913-4.035-5.489-4.035-.818 0-1.596.171-2.301.48-.273.119-.449.389-.449.687l0 9.583c0 .414.336.75.75.75l8 0zM7.25 8l0 9c0 .414.336.75.75.75.414 0 .75-.336.75-.75l0-9c0-.414-.336-.75-.75-.75-.414 0-.75.336-.75.75zM4.25 10l0 7c0 .414.336.75.75.75.414 0 .75-.336.75-.75l0-7c0-.414-.336-.75-.75-.75-.414 0-.75.336-.75.75zM1.25 12l0 5c0 .414.336.75.75.75.414 0 .75-.336.75-.75l0-5c0-.414-.336-.75-.75-.75-.414 0-.75.336-.75.75z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||sndcdn.com^",
|
||||
"||soundcloud.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "spotify",
|
||||
Name: "Spotify",
|
||||
|
||||
102
internal/home/client.go
Normal file
102
internal/home/client.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
)
|
||||
|
||||
// Client contains information about persistent clients.
|
||||
type Client struct {
|
||||
// upstreamConfig is the custom upstream config for this client. If
|
||||
// it's nil, it has not been initialized yet. If it's non-nil and
|
||||
// empty, there are no valid upstreams. If it's non-nil and non-empty,
|
||||
// these upstream must be used.
|
||||
upstreamConfig *proxy.UpstreamConfig
|
||||
|
||||
Name string
|
||||
|
||||
IDs []string
|
||||
Tags []string
|
||||
BlockedServices []string
|
||||
Upstreams []string
|
||||
|
||||
UseOwnSettings bool
|
||||
FilteringEnabled bool
|
||||
SafeSearchEnabled bool
|
||||
SafeBrowsingEnabled bool
|
||||
ParentalEnabled bool
|
||||
UseOwnBlockedServices bool
|
||||
}
|
||||
|
||||
// closeUpstreams closes the client-specific upstream config of c if any.
|
||||
func (c *Client) closeUpstreams() (err error) {
|
||||
if c.upstreamConfig != nil {
|
||||
err = c.upstreamConfig.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clientSource represents the source from which the information about the
|
||||
// client has been obtained.
|
||||
type clientSource uint
|
||||
|
||||
// Clients information sources. The order determines the priority.
|
||||
const (
|
||||
ClientSourceNone clientSource = iota
|
||||
ClientSourceWHOIS
|
||||
ClientSourceARP
|
||||
ClientSourceRDNS
|
||||
ClientSourceDHCP
|
||||
ClientSourceHostsFile
|
||||
ClientSourcePersistent
|
||||
)
|
||||
|
||||
// type check
|
||||
var _ fmt.Stringer = clientSource(0)
|
||||
|
||||
// String returns a human-readable name of cs.
|
||||
func (cs clientSource) String() (s string) {
|
||||
switch cs {
|
||||
case ClientSourceWHOIS:
|
||||
return "WHOIS"
|
||||
case ClientSourceARP:
|
||||
return "ARP"
|
||||
case ClientSourceRDNS:
|
||||
return "rDNS"
|
||||
case ClientSourceDHCP:
|
||||
return "DHCP"
|
||||
case ClientSourceHostsFile:
|
||||
return "etc/hosts"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ encoding.TextMarshaler = clientSource(0)
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler for the clientSource.
|
||||
func (cs clientSource) MarshalText() (text []byte, err error) {
|
||||
return []byte(cs.String()), nil
|
||||
}
|
||||
|
||||
// RuntimeClient is a client information about which has been obtained using the
|
||||
// source described in the Source field.
|
||||
type RuntimeClient struct {
|
||||
WHOISInfo *RuntimeClientWHOISInfo
|
||||
Host string
|
||||
Source clientSource
|
||||
}
|
||||
|
||||
// RuntimeClientWHOISInfo is the filtered WHOIS data for a runtime client.
|
||||
type RuntimeClientWHOISInfo struct {
|
||||
City string `json:"city,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Orgname string `json:"orgname,omitempty"`
|
||||
}
|
||||
@@ -2,11 +2,9 @@ package home
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -26,122 +24,16 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const clientsUpdatePeriod = 10 * time.Minute
|
||||
|
||||
var webHandlersRegistered = false
|
||||
|
||||
// Client contains information about persistent clients.
|
||||
type Client struct {
|
||||
// upstreamConfig is the custom upstream config for this client. If
|
||||
// it's nil, it has not been initialized yet. If it's non-nil and
|
||||
// empty, there are no valid upstreams. If it's non-nil and non-empty,
|
||||
// these upstream must be used.
|
||||
upstreamConfig *proxy.UpstreamConfig
|
||||
|
||||
Name string
|
||||
|
||||
IDs []string
|
||||
Tags []string
|
||||
BlockedServices []string
|
||||
Upstreams []string
|
||||
|
||||
UseOwnSettings bool
|
||||
FilteringEnabled bool
|
||||
SafeSearchEnabled bool
|
||||
SafeBrowsingEnabled bool
|
||||
ParentalEnabled bool
|
||||
UseOwnBlockedServices bool
|
||||
}
|
||||
|
||||
// closeUpstreams closes the client-specific upstream config of c if any.
|
||||
func (c *Client) closeUpstreams() (err error) {
|
||||
if c.upstreamConfig != nil {
|
||||
err = c.upstreamConfig.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type clientSource uint
|
||||
|
||||
// Clients information sources. The order determines the priority.
|
||||
const (
|
||||
ClientSourceNone clientSource = iota
|
||||
ClientSourceWHOIS
|
||||
ClientSourceARP
|
||||
ClientSourceRDNS
|
||||
ClientSourceDHCP
|
||||
ClientSourceHostsFile
|
||||
ClientSourcePersistent
|
||||
)
|
||||
|
||||
// type check
|
||||
var _ fmt.Stringer = clientSource(0)
|
||||
|
||||
// String returns a human-readable name of cs.
|
||||
func (cs clientSource) String() (s string) {
|
||||
switch cs {
|
||||
case ClientSourceWHOIS:
|
||||
return "WHOIS"
|
||||
case ClientSourceARP:
|
||||
return "ARP"
|
||||
case ClientSourceRDNS:
|
||||
return "rDNS"
|
||||
case ClientSourceDHCP:
|
||||
return "DHCP"
|
||||
case ClientSourceHostsFile:
|
||||
return "etc/hosts"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ encoding.TextMarshaler = clientSource(0)
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler for the clientSource.
|
||||
func (cs clientSource) MarshalText() (text []byte, err error) {
|
||||
return []byte(cs.String()), nil
|
||||
}
|
||||
|
||||
// clientSourceConf is used to configure where the runtime clients will be
|
||||
// obtained from.
|
||||
type clientSourcesConf struct {
|
||||
WHOIS bool `yaml:"whois"`
|
||||
ARP bool `yaml:"arp"`
|
||||
RDNS bool `yaml:"rdns"`
|
||||
DHCP bool `yaml:"dhcp"`
|
||||
HostsFile bool `yaml:"hosts"`
|
||||
}
|
||||
|
||||
// RuntimeClient information
|
||||
type RuntimeClient struct {
|
||||
WHOISInfo *RuntimeClientWHOISInfo
|
||||
Host string
|
||||
Source clientSource
|
||||
}
|
||||
|
||||
// RuntimeClientWHOISInfo is the filtered WHOIS data for a runtime client.
|
||||
type RuntimeClientWHOISInfo struct {
|
||||
City string `json:"city,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Orgname string `json:"orgname,omitempty"`
|
||||
}
|
||||
|
||||
// clientsContainer is the storage of all runtime and persistent clients.
|
||||
type clientsContainer struct {
|
||||
// TODO(a.garipov): Perhaps use a number of separate indices for
|
||||
// different types (string, netip.Addr, and so on).
|
||||
// TODO(a.garipov): Perhaps use a number of separate indices for different
|
||||
// types (string, netip.Addr, and so on).
|
||||
list map[string]*Client // name -> client
|
||||
idIndex map[string]*Client // ID -> client
|
||||
|
||||
// ipToRC is the IP address to *RuntimeClient map.
|
||||
ipToRC map[netip.Addr]*RuntimeClient
|
||||
|
||||
lock sync.Mutex
|
||||
|
||||
allTags *stringutil.Set
|
||||
|
||||
// dhcpServer is used for looking up clients IP addresses by MAC addresses
|
||||
@@ -157,7 +49,16 @@ type clientsContainer struct {
|
||||
// arpdb stores the neighbors retrieved from ARP.
|
||||
arpdb aghnet.ARPDB
|
||||
|
||||
testing bool // if TRUE, this object is used for internal tests
|
||||
// lock protects all fields.
|
||||
//
|
||||
// TODO(a.garipov): Use a pointer and describe which fields are protected in
|
||||
// more detail.
|
||||
lock sync.Mutex
|
||||
|
||||
// testing is a flag that disables some features for internal tests.
|
||||
//
|
||||
// TODO(a.garipov): Awful. Remove.
|
||||
testing bool
|
||||
}
|
||||
|
||||
// Init initializes clients container
|
||||
@@ -203,24 +104,34 @@ func (clients *clientsContainer) handleHostsUpdates() {
|
||||
}
|
||||
}
|
||||
|
||||
// Start - start the module
|
||||
// webHandlersRegistered prevents a [clientsContainer] from regisering its web
|
||||
// handlers more than once.
|
||||
//
|
||||
// TODO(a.garipov): Refactor HTTP handler registration logic.
|
||||
var webHandlersRegistered = false
|
||||
|
||||
// Start starts the clients container.
|
||||
func (clients *clientsContainer) Start() {
|
||||
if !clients.testing {
|
||||
if !webHandlersRegistered {
|
||||
webHandlersRegistered = true
|
||||
clients.registerWebHandlers()
|
||||
}
|
||||
go clients.periodicUpdate()
|
||||
if clients.testing {
|
||||
return
|
||||
}
|
||||
|
||||
if !webHandlersRegistered {
|
||||
webHandlersRegistered = true
|
||||
clients.registerWebHandlers()
|
||||
}
|
||||
|
||||
go clients.periodicUpdate()
|
||||
}
|
||||
|
||||
// Reload reloads runtime clients.
|
||||
func (clients *clientsContainer) Reload() {
|
||||
// reloadARP reloads runtime clients from ARP, if configured.
|
||||
func (clients *clientsContainer) reloadARP() {
|
||||
if clients.arpdb != nil {
|
||||
clients.addFromSystemARP()
|
||||
}
|
||||
}
|
||||
|
||||
// clientObject is the YAML representation of a persistent client.
|
||||
type clientObject struct {
|
||||
Name string `yaml:"name"`
|
||||
|
||||
@@ -271,7 +182,7 @@ func (clients *clientsContainer) addFromConfig(objects []*clientObject) {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(cli.Tags)
|
||||
slices.Sort(cli.Tags)
|
||||
|
||||
_, err := clients.Add(cli)
|
||||
if err != nil {
|
||||
@@ -311,17 +222,22 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
|
||||
// above loop can generate different orderings when writing to the config
|
||||
// file: this produces lots of diffs in config files, so sort objects by
|
||||
// name before writing.
|
||||
sort.Slice(objs, func(i, j int) bool { return objs[i].Name < objs[j].Name })
|
||||
slices.SortStableFunc(objs, func(a, b *clientObject) (sortsBefore bool) {
|
||||
return a.Name < b.Name
|
||||
})
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
// arpClientsUpdatePeriod defines how often ARP clients are updated.
|
||||
const arpClientsUpdatePeriod = 10 * time.Minute
|
||||
|
||||
func (clients *clientsContainer) periodicUpdate() {
|
||||
defer log.OnPanic("clients container")
|
||||
|
||||
for {
|
||||
clients.Reload()
|
||||
time.Sleep(clientsUpdatePeriod)
|
||||
clients.reloadARP()
|
||||
time.Sleep(arpClientsUpdatePeriod)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +400,8 @@ func (clients *clientsContainer) findUpstreams(
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// findLocked searches for a client by its ID. For internal use only.
|
||||
// findLocked searches for a client by its ID. clients.lock is expected to be
|
||||
// locked.
|
||||
func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||
c, ok = clients.idIndex[id]
|
||||
if ok {
|
||||
@@ -498,13 +415,13 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||
|
||||
for _, c = range clients.list {
|
||||
for _, id := range c.IDs {
|
||||
var n netip.Prefix
|
||||
n, err = netip.ParsePrefix(id)
|
||||
var subnet netip.Prefix
|
||||
subnet, err = netip.ParsePrefix(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if n.Contains(ip) {
|
||||
if subnet.Contains(ip) {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
@@ -514,20 +431,25 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
macFound := clients.dhcpServer.FindMACbyIP(ip.AsSlice())
|
||||
if macFound == nil {
|
||||
return clients.findDHCP(ip)
|
||||
}
|
||||
|
||||
// findDHCP searches for a client by its MAC, if the DHCP server is active and
|
||||
// there is such client. clients.lock is expected to be locked.
|
||||
func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *Client, ok bool) {
|
||||
foundMAC := clients.dhcpServer.FindMACbyIP(ip)
|
||||
if foundMAC == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for _, c = range clients.list {
|
||||
for _, id := range c.IDs {
|
||||
var mac net.HardwareAddr
|
||||
mac, err = net.ParseMAC(id)
|
||||
mac, err := net.ParseMAC(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(mac, macFound) {
|
||||
if bytes.Equal(mac, foundMAC) {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
@@ -564,24 +486,13 @@ func (clients *clientsContainer) check(c *Client) (err error) {
|
||||
}
|
||||
|
||||
for i, id := range c.IDs {
|
||||
// Normalize structured data.
|
||||
var (
|
||||
ip netip.Addr
|
||||
n netip.Prefix
|
||||
mac net.HardwareAddr
|
||||
)
|
||||
|
||||
if ip, err = netip.ParseAddr(id); err == nil {
|
||||
c.IDs[i] = ip.String()
|
||||
} else if n, err = netip.ParsePrefix(id); err == nil {
|
||||
c.IDs[i] = n.String()
|
||||
} else if mac, err = net.ParseMAC(id); err == nil {
|
||||
c.IDs[i] = mac.String()
|
||||
} else if err = dnsforward.ValidateClientID(id); err == nil {
|
||||
c.IDs[i] = strings.ToLower(id)
|
||||
} else {
|
||||
return fmt.Errorf("invalid clientid at index %d: %q", i, id)
|
||||
var norm string
|
||||
norm, err = normalizeClientIdentifier(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
c.IDs[i] = norm
|
||||
}
|
||||
|
||||
for _, t := range c.Tags {
|
||||
@@ -590,7 +501,7 @@ func (clients *clientsContainer) check(c *Client) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(c.Tags)
|
||||
slices.Sort(c.Tags)
|
||||
|
||||
err = dnsforward.ValidateUpstreams(c.Upstreams)
|
||||
if err != nil {
|
||||
@@ -600,6 +511,35 @@ func (clients *clientsContainer) check(c *Client) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeClientIdentifier returns a normalized version of idStr. If idStr
|
||||
// cannot be normalized, it returns an error.
|
||||
func normalizeClientIdentifier(idStr string) (norm string, err error) {
|
||||
if idStr == "" {
|
||||
return "", errors.Error("clientid is empty")
|
||||
}
|
||||
|
||||
var ip netip.Addr
|
||||
if ip, err = netip.ParseAddr(idStr); err == nil {
|
||||
return ip.String(), nil
|
||||
}
|
||||
|
||||
var subnet netip.Prefix
|
||||
if subnet, err = netip.ParsePrefix(idStr); err == nil {
|
||||
return subnet.String(), nil
|
||||
}
|
||||
|
||||
var mac net.HardwareAddr
|
||||
if mac, err = net.ParseMAC(idStr); err == nil {
|
||||
return mac.String(), nil
|
||||
}
|
||||
|
||||
if err = dnsforward.ValidateClientID(idStr); err == nil {
|
||||
return strings.ToLower(idStr), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("bad client identifier %q", idStr)
|
||||
}
|
||||
|
||||
// Add adds a new client object. ok is false if such client already exists or
|
||||
// if an error occurred.
|
||||
func (clients *clientsContainer) Add(c *Client) (ok bool, err error) {
|
||||
@@ -665,21 +605,6 @@ func (clients *clientsContainer) Del(name string) (ok bool) {
|
||||
return true
|
||||
}
|
||||
|
||||
// equalStringSlices returns true if the slices are equal.
|
||||
func equalStringSlices(a, b []string) (ok bool) {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Update updates a client by its name.
|
||||
func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
||||
err = clients.check(c)
|
||||
@@ -703,22 +628,11 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Second, check the IP index.
|
||||
if !equalStringSlices(prev.IDs, c.IDs) {
|
||||
for _, id := range c.IDs {
|
||||
c2, ok2 := clients.idIndex[id]
|
||||
if ok2 && c2 != prev {
|
||||
return fmt.Errorf("another client uses the same id (%q): %q", id, c2.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Update ID index.
|
||||
for _, id := range prev.IDs {
|
||||
delete(clients.idIndex, id)
|
||||
}
|
||||
for _, id := range c.IDs {
|
||||
clients.idIndex[id] = prev
|
||||
}
|
||||
// Second, update the ID index.
|
||||
err = clients.updateIDIndex(prev, c.IDs)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
// Update name index.
|
||||
@@ -738,6 +652,32 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateIDIndex updates the ID index data for cli using the information from
|
||||
// newIDs.
|
||||
func (clients *clientsContainer) updateIDIndex(cli *Client, newIDs []string) (err error) {
|
||||
if slices.Equal(cli.IDs, newIDs) {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, id := range newIDs {
|
||||
existing, ok := clients.idIndex[id]
|
||||
if ok && existing != cli {
|
||||
return fmt.Errorf("id %q is used by client with name %q", id, existing.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the IDs in the index.
|
||||
for _, id := range cli.IDs {
|
||||
delete(clients.idIndex, id)
|
||||
}
|
||||
|
||||
for _, id := range newIDs {
|
||||
clients.idIndex[id] = cli
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setWHOISInfo sets the WHOIS information for a client.
|
||||
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) {
|
||||
clients.lock.Lock()
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
@@ -21,6 +20,7 @@ import (
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/google/renameio/maybe"
|
||||
"golang.org/x/exp/slices"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -75,11 +75,21 @@ type osConfig struct {
|
||||
|
||||
type clientsConfig struct {
|
||||
// Sources defines the set of sources to fetch the runtime clients from.
|
||||
Sources *clientSourcesConf `yaml:"runtime_sources"`
|
||||
Sources *clientSourcesConfig `yaml:"runtime_sources"`
|
||||
// Persistent are the configured clients.
|
||||
Persistent []*clientObject `yaml:"persistent"`
|
||||
}
|
||||
|
||||
// clientSourceConfig is used to configure where the runtime clients will be
|
||||
// obtained from.
|
||||
type clientSourcesConfig struct {
|
||||
WHOIS bool `yaml:"whois"`
|
||||
ARP bool `yaml:"arp"`
|
||||
RDNS bool `yaml:"rdns"`
|
||||
DHCP bool `yaml:"dhcp"`
|
||||
HostsFile bool `yaml:"hosts"`
|
||||
}
|
||||
|
||||
// configuration is loaded from YAML
|
||||
// field ordering is important -- yaml fields will mirror ordering from here
|
||||
type configuration struct {
|
||||
@@ -275,6 +285,12 @@ var config = &configuration{
|
||||
TrustedProxies: []string{"127.0.0.0/8", "::1/128"},
|
||||
CacheSize: 4 * 1024 * 1024,
|
||||
|
||||
EDNSClientSubnet: &dnsforward.EDNSClientSubnet{
|
||||
CustomIP: "",
|
||||
Enabled: false,
|
||||
UseCustom: false,
|
||||
},
|
||||
|
||||
// set default maximum concurrent queries to 300
|
||||
// we introduced a default limit due to this:
|
||||
// https://github.com/AdguardTeam/AdGuardHome/issues/2015#issuecomment-674041912
|
||||
@@ -336,7 +352,7 @@ var config = &configuration{
|
||||
},
|
||||
},
|
||||
Clients: &clientsConfig{
|
||||
Sources: &clientSourcesConf{
|
||||
Sources: &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: true,
|
||||
@@ -490,7 +506,7 @@ func (c *configuration) write() (err error) {
|
||||
config.Stats.Interval = statsConf.LimitDays
|
||||
config.Stats.Enabled = statsConf.Enabled
|
||||
config.Stats.Ignored = statsConf.Ignored.Values()
|
||||
sort.Strings(config.Stats.Ignored)
|
||||
slices.Sort(config.Stats.Ignored)
|
||||
}
|
||||
|
||||
if Context.queryLog != nil {
|
||||
@@ -502,7 +518,7 @@ func (c *configuration) write() (err error) {
|
||||
config.QueryLog.Interval = timeutil.Duration{Duration: dc.RotationIvl}
|
||||
config.QueryLog.MemSize = dc.MemSize
|
||||
config.QueryLog.Ignored = dc.Ignored.Values()
|
||||
sort.Strings(config.QueryLog.Ignored)
|
||||
slices.Sort(config.Stats.Ignored)
|
||||
}
|
||||
|
||||
if Context.filters != nil {
|
||||
|
||||
@@ -98,7 +98,7 @@ func requestVersionInfo(resp *versionResponse, recheck bool) (err error) {
|
||||
if err != nil {
|
||||
vcu := Context.updater.VersionCheckURL()
|
||||
|
||||
return fmt.Errorf("getting version info from %s: %s", vcu, err)
|
||||
return fmt.Errorf("getting version info from %s: %w", vcu, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -125,7 +125,7 @@ func Main(clientBuildFS fs.FS) {
|
||||
log.Info("Received signal %q", sig)
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
Context.clients.Reload()
|
||||
Context.clients.reloadARP()
|
||||
Context.tls.reload()
|
||||
default:
|
||||
cleanup(context.Background())
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
// currentSchemaVersion is the current schema version.
|
||||
const currentSchemaVersion = 16
|
||||
const currentSchemaVersion = 17
|
||||
|
||||
// These aliases are provided for convenience.
|
||||
type (
|
||||
@@ -89,6 +89,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
|
||||
upgradeSchema13to14,
|
||||
upgradeSchema14to15,
|
||||
upgradeSchema15to16,
|
||||
upgradeSchema16to17,
|
||||
}
|
||||
|
||||
n := 0
|
||||
@@ -792,7 +793,7 @@ func upgradeSchema13to14(diskConf yobj) (err error) {
|
||||
|
||||
diskConf["clients"] = yobj{
|
||||
"persistent": clientsVal,
|
||||
"runtime_sources": &clientSourcesConf{
|
||||
"runtime_sources": &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: rdnsSrc,
|
||||
@@ -892,19 +893,56 @@ func upgradeSchema15to16(diskConf yobj) (err error) {
|
||||
"ignored": []any{},
|
||||
}
|
||||
|
||||
k := "statistics_interval"
|
||||
v, has := dns[k]
|
||||
const field = "statistics_interval"
|
||||
v, has := dns[field]
|
||||
if has {
|
||||
stats["enabled"] = v != 0
|
||||
stats["interval"] = v
|
||||
}
|
||||
delete(dns, k)
|
||||
delete(dns, field)
|
||||
|
||||
diskConf["statistics"] = stats
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeSchema16to17 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
// 'dns':
|
||||
// 'edns_client_subnet': false
|
||||
//
|
||||
// # AFTER:
|
||||
// 'dns':
|
||||
// 'edns_client_subnet':
|
||||
// 'enabled': false
|
||||
// 'use_custom': false
|
||||
// 'custom_ip': ""
|
||||
func upgradeSchema16to17(diskConf yobj) (err error) {
|
||||
log.Printf("Upgrade yaml: 16 to 17")
|
||||
diskConf["schema_version"] = 17
|
||||
|
||||
dnsVal, ok := diskConf["dns"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
dns, ok := dnsVal.(yobj)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
|
||||
}
|
||||
|
||||
const field = "edns_client_subnet"
|
||||
|
||||
dns[field] = map[string]any{
|
||||
"enabled": dns[field] == true,
|
||||
"use_custom": false,
|
||||
"custom_ip": "",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Replace with log.Output when we port it to our logging
|
||||
// package.
|
||||
func funcName() string {
|
||||
|
||||
@@ -579,7 +579,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
|
||||
// The clients field will be added anyway.
|
||||
"clients": yobj{
|
||||
"persistent": yarr{},
|
||||
"runtime_sources": &clientSourcesConf{
|
||||
"runtime_sources": &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: false,
|
||||
@@ -597,7 +597,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
|
||||
"schema_version": newSchemaVer,
|
||||
"clients": yobj{
|
||||
"persistent": []*clientObject{testClient},
|
||||
"runtime_sources": &clientSourcesConf{
|
||||
"runtime_sources": &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: false,
|
||||
@@ -618,7 +618,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
|
||||
"schema_version": newSchemaVer,
|
||||
"clients": yobj{
|
||||
"persistent": []*clientObject{testClient},
|
||||
"runtime_sources": &clientSourcesConf{
|
||||
"runtime_sources": &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: true,
|
||||
@@ -747,3 +747,64 @@ func TestUpgradeSchema15to16(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgradeSchema16to17(t *testing.T) {
|
||||
const newSchemaVer = 17
|
||||
|
||||
defaultWantObj := yobj{
|
||||
"dns": map[string]any{
|
||||
"edns_client_subnet": map[string]any{
|
||||
"enabled": false,
|
||||
"use_custom": false,
|
||||
"custom_ip": "",
|
||||
},
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
in yobj
|
||||
want yobj
|
||||
name string
|
||||
}{{
|
||||
in: yobj{
|
||||
"dns": map[string]any{
|
||||
"edns_client_subnet": false,
|
||||
},
|
||||
},
|
||||
want: defaultWantObj,
|
||||
name: "basic",
|
||||
}, {
|
||||
in: yobj{
|
||||
"dns": map[string]any{},
|
||||
},
|
||||
want: defaultWantObj,
|
||||
name: "default_values",
|
||||
}, {
|
||||
in: yobj{
|
||||
"dns": map[string]any{
|
||||
"edns_client_subnet": true,
|
||||
},
|
||||
},
|
||||
want: yobj{
|
||||
"dns": map[string]any{
|
||||
"edns_client_subnet": map[string]any{
|
||||
"enabled": true,
|
||||
"use_custom": false,
|
||||
"custom_ip": "",
|
||||
},
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
name: "is_true",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := upgradeSchema16to17(tc.in)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.want, tc.in)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,55 +247,20 @@ var resultHandlers = map[string]logEntryHandler{
|
||||
}
|
||||
|
||||
func decodeResultRuleKey(key string, i int, dec *json.Decoder, ent *logEntry) {
|
||||
var vToken json.Token
|
||||
switch key {
|
||||
case "FilterListID":
|
||||
vToken, err := dec.Token()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Debug("decodeResultRuleKey %s err: %s", key, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(ent.Result.Rules) < i+1 {
|
||||
ent.Result.Rules = append(ent.Result.Rules, &filtering.ResultRule{})
|
||||
}
|
||||
|
||||
ent.Result.Rules, vToken = decodeVTokenAndAddRule(key, i, dec, ent.Result.Rules)
|
||||
if n, ok := vToken.(json.Number); ok {
|
||||
ent.Result.Rules[i].FilterListID, _ = n.Int64()
|
||||
}
|
||||
case "IP":
|
||||
vToken, err := dec.Token()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Debug("decodeResultRuleKey %s err: %s", key, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(ent.Result.Rules) < i+1 {
|
||||
ent.Result.Rules = append(ent.Result.Rules, &filtering.ResultRule{})
|
||||
}
|
||||
|
||||
ent.Result.Rules, vToken = decodeVTokenAndAddRule(key, i, dec, ent.Result.Rules)
|
||||
if ipStr, ok := vToken.(string); ok {
|
||||
ent.Result.Rules[i].IP = net.ParseIP(ipStr)
|
||||
}
|
||||
case "Text":
|
||||
vToken, err := dec.Token()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Debug("decodeResultRuleKey %s err: %s", key, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(ent.Result.Rules) < i+1 {
|
||||
ent.Result.Rules = append(ent.Result.Rules, &filtering.ResultRule{})
|
||||
}
|
||||
|
||||
ent.Result.Rules, vToken = decodeVTokenAndAddRule(key, i, dec, ent.Result.Rules)
|
||||
if s, ok := vToken.(string); ok {
|
||||
ent.Result.Rules[i].Text = s
|
||||
}
|
||||
@@ -304,6 +269,30 @@ func decodeResultRuleKey(key string, i int, dec *json.Decoder, ent *logEntry) {
|
||||
}
|
||||
}
|
||||
|
||||
func decodeVTokenAndAddRule(
|
||||
key string,
|
||||
i int,
|
||||
dec *json.Decoder,
|
||||
rules []*filtering.ResultRule,
|
||||
) (newRules []*filtering.ResultRule, vToken json.Token) {
|
||||
newRules = rules
|
||||
|
||||
vToken, err := dec.Token()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Debug("decodeResultRuleKey %s err: %s", key, err)
|
||||
}
|
||||
|
||||
return newRules, nil
|
||||
}
|
||||
|
||||
if len(rules) < i+1 {
|
||||
newRules = append(newRules, &filtering.ResultRule{})
|
||||
}
|
||||
|
||||
return newRules, vToken
|
||||
}
|
||||
|
||||
func decodeResultRules(dec *json.Decoder, ent *logEntry) {
|
||||
for {
|
||||
delimToken, err := dec.Token()
|
||||
|
||||
@@ -54,10 +54,7 @@ func (l *queryLog) handleQueryLog(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// search for the log entries
|
||||
entries, oldest := l.search(params)
|
||||
|
||||
// convert log entries to JSON
|
||||
data := l.entriesToJSON(entries, oldest)
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, data)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package querylog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -117,7 +116,7 @@ func (l *queryLog) setMsgData(entry *logEntry, jsonEntry jobject) {
|
||||
// it from there as well.
|
||||
jsonEntry["answer_dnssec"] = entry.AuthenticatedData || msg.AuthenticatedData
|
||||
|
||||
if a := answerToMap(msg); a != nil {
|
||||
if a := answerToJSON(msg); a != nil {
|
||||
jsonEntry["answer"] = a
|
||||
}
|
||||
}
|
||||
@@ -136,7 +135,7 @@ func (l *queryLog) setOrigAns(entry *logEntry, jsonEntry jobject) {
|
||||
return
|
||||
}
|
||||
|
||||
if a := answerToMap(orig); a != nil {
|
||||
if a := answerToJSON(orig); a != nil {
|
||||
jsonEntry["original_answer"] = a
|
||||
}
|
||||
}
|
||||
@@ -159,55 +158,24 @@ type dnsAnswer struct {
|
||||
TTL uint32 `json:"ttl"`
|
||||
}
|
||||
|
||||
func answerToMap(a *dns.Msg) (answers []*dnsAnswer) {
|
||||
if a == nil || len(a.Answer) == 0 {
|
||||
// answerToJSON converts the answer records of msg, if any, to their JSON form.
|
||||
func answerToJSON(msg *dns.Msg) (answers []*dnsAnswer) {
|
||||
if msg == nil || len(msg.Answer) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
answers = make([]*dnsAnswer, 0, len(a.Answer))
|
||||
for _, k := range a.Answer {
|
||||
header := k.Header()
|
||||
answer := &dnsAnswer{
|
||||
answers = make([]*dnsAnswer, 0, len(msg.Answer))
|
||||
for _, rr := range msg.Answer {
|
||||
header := rr.Header()
|
||||
a := &dnsAnswer{
|
||||
Type: dns.TypeToString[header.Rrtype],
|
||||
TTL: header.Ttl,
|
||||
// Remove the header string from the answer value since it's mostly
|
||||
// unnecessary in the log.
|
||||
Value: strings.TrimPrefix(rr.String(), header.String()),
|
||||
TTL: header.Ttl,
|
||||
}
|
||||
|
||||
// Some special treatment for some well-known types.
|
||||
//
|
||||
// TODO(a.garipov): Consider just calling String() for everyone
|
||||
// instead.
|
||||
switch v := k.(type) {
|
||||
case nil:
|
||||
// Probably unlikely, but go on.
|
||||
case *dns.A:
|
||||
answer.Value = v.A.String()
|
||||
case *dns.AAAA:
|
||||
answer.Value = v.AAAA.String()
|
||||
case *dns.MX:
|
||||
answer.Value = fmt.Sprintf("%v %v", v.Preference, v.Mx)
|
||||
case *dns.CNAME:
|
||||
answer.Value = v.Target
|
||||
case *dns.NS:
|
||||
answer.Value = v.Ns
|
||||
case *dns.SPF:
|
||||
answer.Value = strings.Join(v.Txt, "\n")
|
||||
case *dns.TXT:
|
||||
answer.Value = strings.Join(v.Txt, "\n")
|
||||
case *dns.PTR:
|
||||
answer.Value = v.Ptr
|
||||
case *dns.SOA:
|
||||
answer.Value = fmt.Sprintf("%v %v %v %v %v %v %v", v.Ns, v.Mbox, v.Serial, v.Refresh, v.Retry, v.Expire, v.Minttl)
|
||||
case *dns.CAA:
|
||||
answer.Value = fmt.Sprintf("%v %v \"%v\"", v.Flag, v.Tag, v.Value)
|
||||
case *dns.HINFO:
|
||||
answer.Value = fmt.Sprintf("\"%v\" \"%v\"", v.Cpu, v.Os)
|
||||
case *dns.RRSIG:
|
||||
answer.Value = fmt.Sprintf("%v %v %v %v %v %v %v %v %v", dns.TypeToString[v.TypeCovered], v.Algorithm, v.Labels, v.OrigTtl, v.Expiration, v.Inception, v.KeyTag, v.SignerName, v.Signature)
|
||||
default:
|
||||
answer.Value = v.String()
|
||||
}
|
||||
|
||||
answers = append(answers, answer)
|
||||
answers = append(answers, a)
|
||||
}
|
||||
|
||||
return answers
|
||||
|
||||
@@ -31,7 +31,8 @@ type queryLog struct {
|
||||
|
||||
// bufferLock protects buffer.
|
||||
bufferLock sync.RWMutex
|
||||
// buffer contains recent log entries.
|
||||
// buffer contains recent log entries. The entries in this buffer must not
|
||||
// be modified.
|
||||
buffer []*logEntry
|
||||
|
||||
fileFlushLock sync.Mutex // synchronize a file-flushing goroutine and main thread
|
||||
@@ -100,6 +101,13 @@ type logEntry struct {
|
||||
AuthenticatedData bool `json:"AD,omitempty"`
|
||||
}
|
||||
|
||||
// shallowClone returns a shallow clone of e.
|
||||
func (e *logEntry) shallowClone() (clone *logEntry) {
|
||||
cloneVal := *e
|
||||
|
||||
return &cloneVal
|
||||
}
|
||||
|
||||
func (l *queryLog) Start() {
|
||||
if l.conf.HTTPRegister != nil {
|
||||
l.initWeb()
|
||||
|
||||
@@ -2,11 +2,8 @@ package querylog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxyutil"
|
||||
@@ -352,72 +349,3 @@ func assertLogEntry(t *testing.T, entry *logEntry, host string, answer, client n
|
||||
ip := proxyutil.IPFromRR(msg.Answer[0]).To16()
|
||||
assert.Equal(t, answer, ip)
|
||||
}
|
||||
|
||||
func testEntries() (entries []*logEntry) {
|
||||
rsrc := rand.NewSource(time.Now().UnixNano())
|
||||
rgen := rand.New(rsrc)
|
||||
|
||||
entries = make([]*logEntry, 1000)
|
||||
for i := range entries {
|
||||
min := rgen.Intn(60)
|
||||
sec := rgen.Intn(60)
|
||||
entries[i] = &logEntry{
|
||||
Time: time.Date(2020, 1, 1, 0, min, sec, 0, time.UTC),
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// logEntriesByTimeDesc is a wrapper over []*logEntry for sorting.
|
||||
//
|
||||
// NOTE(a.garipov): Weirdly enough, on my machine this gets consistently
|
||||
// outperformed by sort.Slice, see the benchmark below. I'm leaving this
|
||||
// implementation here, in tests, in case we want to make sure it outperforms on
|
||||
// most machines, but for now this is unused in the actual code.
|
||||
type logEntriesByTimeDesc []*logEntry
|
||||
|
||||
// Len implements the sort.Interface interface for logEntriesByTimeDesc.
|
||||
func (les logEntriesByTimeDesc) Len() (n int) { return len(les) }
|
||||
|
||||
// Less implements the sort.Interface interface for logEntriesByTimeDesc.
|
||||
func (les logEntriesByTimeDesc) Less(i, j int) (less bool) {
|
||||
return les[i].Time.After(les[j].Time)
|
||||
}
|
||||
|
||||
// Swap implements the sort.Interface interface for logEntriesByTimeDesc.
|
||||
func (les logEntriesByTimeDesc) Swap(i, j int) { les[i], les[j] = les[j], les[i] }
|
||||
|
||||
func BenchmarkLogEntry_sort(b *testing.B) {
|
||||
b.Run("methods", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
entries := testEntries()
|
||||
b.StartTimer()
|
||||
|
||||
sort.Stable(logEntriesByTimeDesc(entries))
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("reflect", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
entries := testEntries()
|
||||
b.StartTimer()
|
||||
|
||||
sort.SliceStable(entries, func(i, j int) (less bool) {
|
||||
return entries[i].Time.After(entries[j].Time)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogEntriesByTime_sort(t *testing.T) {
|
||||
entries := testEntries()
|
||||
sort.Sort(logEntriesByTimeDesc(entries))
|
||||
|
||||
for i := range entries[1:] {
|
||||
assert.False(t, entries[i+1].Time.After(entries[i].Time),
|
||||
"%s %s", entries[i+1].Time, entries[i].Time)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package querylog
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// client finds the client info, if any, by its ClientID and IP address,
|
||||
@@ -52,7 +52,9 @@ func (l *queryLog) searchMemory(params *searchParams, cache clientCache) (entrie
|
||||
// Go through the buffer in the reverse order, from newer to older.
|
||||
var err error
|
||||
for i := len(l.buffer) - 1; i >= 0; i-- {
|
||||
e := l.buffer[i]
|
||||
// A shallow clone is enough, since the only thing that this loop
|
||||
// modifies is the client field.
|
||||
e := l.buffer[i].shallowClone()
|
||||
|
||||
e.client, err = l.client(e.ClientID, e.IP.String(), cache)
|
||||
if err != nil {
|
||||
@@ -98,8 +100,8 @@ func (l *queryLog) search(params *searchParams) (entries []*logEntry, oldest tim
|
||||
// weird on the frontend.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2293.
|
||||
sort.SliceStable(entries, func(i, j int) (less bool) {
|
||||
return entries[i].Time.After(entries[j].Time)
|
||||
slices.SortStableFunc(entries, func(a, b *logEntry) (sortsBefore bool) {
|
||||
return a.Time.After(b.Time)
|
||||
})
|
||||
|
||||
if params.offset > 0 {
|
||||
@@ -130,7 +132,7 @@ func (l *queryLog) search(params *searchParams) (entries []*logEntry, oldest tim
|
||||
// searchFiles looks up log records from all log files. It optionally uses the
|
||||
// client cache, if provided. searchFiles does not scan more than
|
||||
// maxFileScanEntries so callers may need to call it several times to get all
|
||||
// results. oldset and total are the time of the oldest processed entry and the
|
||||
// results. oldest and total are the time of the oldest processed entry and the
|
||||
// total number of processed entries, including discarded ones, correspondingly.
|
||||
func (l *queryLog) searchFiles(
|
||||
params *searchParams,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package querylog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
@@ -118,7 +119,7 @@ func (c *searchCriterion) match(entry *logEntry) bool {
|
||||
case ctTerm:
|
||||
return c.ctDomainOrClientCase(entry)
|
||||
case ctFilteringStatus:
|
||||
return c.ctFilteringStatusCase(entry.Result)
|
||||
return c.ctFilteringStatusCase(entry.Result.Reason, entry.Result.IsFiltered)
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -141,54 +142,70 @@ func (c *searchCriterion) ctDomainOrClientCase(e *logEntry) bool {
|
||||
return ctDomainOrClientCaseNonStrict(c.value, c.asciiVal, clientID, name, host, ip)
|
||||
}
|
||||
|
||||
func (c *searchCriterion) ctFilteringStatusCase(res filtering.Result) bool {
|
||||
// ctFilteringStatusCase returns true if the result matches the value.
|
||||
func (c *searchCriterion) ctFilteringStatusCase(
|
||||
reason filtering.Reason,
|
||||
isFiltered bool,
|
||||
) (matched bool) {
|
||||
switch c.value {
|
||||
case filteringStatusAll:
|
||||
return true
|
||||
|
||||
case filteringStatusFiltered:
|
||||
return res.IsFiltered ||
|
||||
res.Reason.In(
|
||||
filtering.NotFilteredAllowList,
|
||||
filtering.Rewritten,
|
||||
filtering.RewrittenAutoHosts,
|
||||
filtering.RewrittenRule,
|
||||
)
|
||||
|
||||
case filteringStatusBlocked:
|
||||
return res.IsFiltered &&
|
||||
res.Reason.In(filtering.FilteredBlockList, filtering.FilteredBlockedService)
|
||||
|
||||
case filteringStatusBlockedService:
|
||||
return res.IsFiltered && res.Reason == filtering.FilteredBlockedService
|
||||
|
||||
case filteringStatusBlockedParental:
|
||||
return res.IsFiltered && res.Reason == filtering.FilteredParental
|
||||
|
||||
case filteringStatusBlockedSafebrowsing:
|
||||
return res.IsFiltered && res.Reason == filtering.FilteredSafeBrowsing
|
||||
|
||||
case
|
||||
filteringStatusBlocked,
|
||||
filteringStatusBlockedParental,
|
||||
filteringStatusBlockedSafebrowsing,
|
||||
filteringStatusBlockedService,
|
||||
filteringStatusFiltered,
|
||||
filteringStatusSafeSearch:
|
||||
return isFiltered && c.isFilteredWithReason(reason)
|
||||
case filteringStatusWhitelisted:
|
||||
return res.Reason == filtering.NotFilteredAllowList
|
||||
|
||||
return reason == filtering.NotFilteredAllowList
|
||||
case filteringStatusRewritten:
|
||||
return res.Reason.In(
|
||||
return reason.In(
|
||||
filtering.Rewritten,
|
||||
filtering.RewrittenAutoHosts,
|
||||
filtering.RewrittenRule,
|
||||
)
|
||||
|
||||
case filteringStatusSafeSearch:
|
||||
return res.IsFiltered && res.Reason == filtering.FilteredSafeSearch
|
||||
|
||||
case filteringStatusProcessed:
|
||||
return !res.Reason.In(
|
||||
return !reason.In(
|
||||
filtering.FilteredBlockList,
|
||||
filtering.FilteredBlockedService,
|
||||
filtering.NotFilteredAllowList,
|
||||
)
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isFilteredWithReason returns true if reason matches the criterion value.
|
||||
// c.value must be one of:
|
||||
//
|
||||
// - filteringStatusBlocked
|
||||
// - filteringStatusBlockedParental
|
||||
// - filteringStatusBlockedSafebrowsing
|
||||
// - filteringStatusBlockedService
|
||||
// - filteringStatusFiltered
|
||||
// - filteringStatusSafeSearch
|
||||
func (c *searchCriterion) isFilteredWithReason(reason filtering.Reason) (matched bool) {
|
||||
switch c.value {
|
||||
case filteringStatusBlocked:
|
||||
return reason.In(filtering.FilteredBlockList, filtering.FilteredBlockedService)
|
||||
case filteringStatusBlockedParental:
|
||||
return reason == filtering.FilteredParental
|
||||
case filteringStatusBlockedSafebrowsing:
|
||||
return reason == filtering.FilteredSafeBrowsing
|
||||
case filteringStatusBlockedService:
|
||||
return reason == filtering.FilteredBlockedService
|
||||
case filteringStatusFiltered:
|
||||
return reason.In(
|
||||
filtering.NotFilteredAllowList,
|
||||
filtering.Rewritten,
|
||||
filtering.RewrittenAutoHosts,
|
||||
filtering.RewrittenRule,
|
||||
)
|
||||
case filteringStatusSafeSearch:
|
||||
return reason == filtering.FilteredSafeSearch
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected value %q", c.value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"go.etcd.io/bbolt"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Rewrite all of this. Add proper error handling and
|
||||
@@ -180,8 +180,8 @@ func convertMapToSlice(m map[string]uint64, max int) (s []countPair) {
|
||||
s = append(s, countPair{Name: k, Count: v})
|
||||
}
|
||||
|
||||
sort.Slice(s, func(i, j int) bool {
|
||||
return s[j].Count < s[i].Count
|
||||
slices.SortFunc(s, func(a, b countPair) (sortsBefore bool) {
|
||||
return a.Count > b.Count
|
||||
})
|
||||
if max > len(s) {
|
||||
max = len(s)
|
||||
|
||||
@@ -9,9 +9,9 @@ require (
|
||||
github.com/kisielk/errcheck v1.6.3
|
||||
github.com/kyoh86/looppointer v0.2.1
|
||||
github.com/securego/gosec/v2 v2.15.0
|
||||
golang.org/x/tools v0.6.0
|
||||
golang.org/x/vuln v0.0.0-20230213165600-1a019b0c7f30
|
||||
honnef.co/go/tools v0.4.1
|
||||
golang.org/x/tools v0.6.1-0.20230217175706-3102dad5faf9
|
||||
golang.org/x/vuln v0.0.0-20230217204342-b91abcc5ae3c
|
||||
honnef.co/go/tools v0.4.2
|
||||
mvdan.cc/gofumpt v0.4.0
|
||||
mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95
|
||||
)
|
||||
|
||||
@@ -97,10 +97,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/vuln v0.0.0-20230213165600-1a019b0c7f30 h1:Q4B8LhSjZGto/+P5REBb4N51ec2H4efRqjBIeJ6nv/Y=
|
||||
golang.org/x/vuln v0.0.0-20230213165600-1a019b0c7f30/go.mod h1:cBP4HMKv0X+x96j8IJWCKk0eqpakBmmHjKGSSC0NaYE=
|
||||
golang.org/x/tools v0.6.1-0.20230217175706-3102dad5faf9 h1:IuFp2CklNBim6OdHXn/1P4VoeKt5pA2jcDKWlboqtlQ=
|
||||
golang.org/x/tools v0.6.1-0.20230217175706-3102dad5faf9/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/vuln v0.0.0-20230217204342-b91abcc5ae3c h1:7/jJkMpaKZMxdyOQ7IP7aPbJQaDk4cOUxtXtWHQ1cSk=
|
||||
golang.org/x/vuln v0.0.0-20230217204342-b91abcc5ae3c/go.mod h1:LTLnfk/dpXDNKsX6aCg/cI4LyCVnTyrQhgV/yLJuly0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -109,8 +109,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.4.1 h1:HPeloSr0mLOEMOkhT9Au5aeki44kvP6ka3v1xIsM6Zo=
|
||||
honnef.co/go/tools v0.4.1/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA=
|
||||
honnef.co/go/tools v0.4.2 h1:6qXr+R5w+ktL5UkwEbPp+fEvfyoMPche6GkOpGHZcLc=
|
||||
honnef.co/go/tools v0.4.2/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA=
|
||||
mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM=
|
||||
mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ=
|
||||
mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95 h1:n/xhncJPSt0YzfOhnyn41XxUdrWQNgmLBG72FE27Fqw=
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package tools
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Make configurable.
|
||||
@@ -81,9 +84,9 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
|
||||
return info, fmt.Errorf("version.json: %w", err)
|
||||
}
|
||||
|
||||
for _, v := range versionJSON {
|
||||
for k, v := range versionJSON {
|
||||
if v == "" {
|
||||
return info, fmt.Errorf("version.json: invalid data")
|
||||
return info, fmt.Errorf("version.json: bad data: value for key %q is empty", k)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,9 +94,9 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
|
||||
info.Announcement = versionJSON["announcement"]
|
||||
info.AnnouncementURL = versionJSON["announcement_url"]
|
||||
|
||||
packageURL, ok := u.downloadURL(versionJSON)
|
||||
if !ok {
|
||||
return info, fmt.Errorf("version.json: packageURL not found")
|
||||
packageURL, key, found := u.downloadURL(versionJSON)
|
||||
if !found {
|
||||
return info, fmt.Errorf("version.json: no package URL: key %q not found in object", key)
|
||||
}
|
||||
|
||||
info.CanAutoUpdate = aghalg.BoolToNullBool(info.NewVersion != u.version)
|
||||
@@ -104,25 +107,40 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// downloadURL returns the download URL for current build.
|
||||
func (u *Updater) downloadURL(json map[string]string) (string, bool) {
|
||||
var key string
|
||||
|
||||
// downloadURL returns the download URL for current build as well as its key in
|
||||
// versionObj. If the key is not found, it additionally prints an informative
|
||||
// log message.
|
||||
func (u *Updater) downloadURL(versionObj map[string]string) (dlURL, key string, ok bool) {
|
||||
if u.goarch == "arm" && u.goarm != "" {
|
||||
key = fmt.Sprintf("download_%s_%sv%s", u.goos, u.goarch, u.goarm)
|
||||
} else if u.goarch == "mips" && u.gomips != "" {
|
||||
} else if isMIPS(u.goarch) && u.gomips != "" {
|
||||
key = fmt.Sprintf("download_%s_%s_%s", u.goos, u.goarch, u.gomips)
|
||||
}
|
||||
|
||||
val, ok := json[key]
|
||||
if !ok {
|
||||
} else {
|
||||
key = fmt.Sprintf("download_%s_%s", u.goos, u.goarch)
|
||||
val, ok = json[key]
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return "", false
|
||||
dlURL, ok = versionObj[key]
|
||||
if ok {
|
||||
return dlURL, key, true
|
||||
}
|
||||
|
||||
return val, true
|
||||
keys := maps.Keys(versionObj)
|
||||
slices.Sort(keys)
|
||||
log.Error("updater: key %q not found; got keys %q", key, keys)
|
||||
|
||||
return "", key, false
|
||||
}
|
||||
|
||||
// isMIPS returns true if arch is any MIPS architecture.
|
||||
func isMIPS(arch string) (ok bool) {
|
||||
switch arch {
|
||||
case
|
||||
"mips",
|
||||
"mips64",
|
||||
"mips64le",
|
||||
"mipsle":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ func (u *Updater) backup(firstRun bool) (err error) {
|
||||
wd := u.workDir
|
||||
err = copySupportingFiles(u.unpackedFiles, wd, u.backupDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", wd, u.backupDir, err)
|
||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", wd, u.backupDir, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -283,7 +283,7 @@ func (u *Updater) backup(firstRun bool) (err error) {
|
||||
func (u *Updater) replace() error {
|
||||
err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.workDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", u.updateDir, u.workDir, err)
|
||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", u.updateDir, u.workDir, err)
|
||||
}
|
||||
|
||||
log.Debug("updater: renaming: %s to %s", u.currentExeName, u.backupExeName)
|
||||
|
||||
Reference in New Issue
Block a user