Compare commits

..

4 Commits

Author SHA1 Message Date
Ildar Kamalov
0cc4923bc3 Merge branch 'master' into 4926-scroll 2022-09-20 13:20:03 +03:00
Ildar Kamalov
23a352d214 Merge branch 'master' into 4926-scroll 2022-09-19 19:35:48 +03:00
Ildar Kamalov
44c64893bb fix 2022-09-19 19:34:54 +03:00
Ildar Kamalov
3f8f8c7253 client: fix tabs scroll on mobile 2022-09-19 19:30:59 +03:00
45 changed files with 950 additions and 1042 deletions

22
.github/stale.yml vendored
View File

@@ -4,17 +4,15 @@
'daysUntilClose': 15
# Issues with these labels will never be considered stale.
'exemptLabels':
- 'bug'
- 'documentation'
- 'enhancement'
- 'feature request'
- 'help wanted'
- 'localization'
- 'needs investigation'
- 'recurrent'
- 'research'
# Set to true to ignore issues in a milestone.
'exemptMilestones': true
- 'bug'
- 'documentation'
- 'enhancement'
- 'feature request'
- 'help wanted'
- 'localization'
- 'needs investigation'
- 'recurrent'
- 'research'
# Label to use when marking an issue as stale.
'staleLabel': 'wontfix'
# Comment to post when marking an issue as stale. Set to `false` to disable.
@@ -24,5 +22,3 @@
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable.
'closeComment': false
# Limit the number of actions per hour.
'limitPerRun': 1

View File

@@ -25,12 +25,7 @@ and this project adheres to
- Support for plain (unencrypted) HTTP/2 ([#4930]). This is useful for AdGuard
Home installations behind a reverse proxy.
### Fixed
- Incorrect path template in DDR responses ([#4927]).
[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
[#4927]: https://github.com/AdguardTeam/AdGuardHome/issues/4927
[#4930]: https://github.com/AdguardTeam/AdGuardHome/issues/4930

View File

@@ -62,7 +62,7 @@ const ClientCell = ({
'white-space--nowrap': isDetailed,
});
const hintClass = classNames('icons mr-4 icon--24 logs__question icon--lightgray', {
const hintClass = classNames('icons mr-4 icon--24 icon--lightgray', {
'my-3': isDetailed,
});

View File

@@ -34,7 +34,7 @@ const DomainCell = ({
'my-3': isDetailed,
});
const privacyIconClass = classNames('icons mx-2 icon--24 d-none d-sm-block logs__question', {
const privacyIconClass = classNames('icons mx-2 icon--24 d-none d-sm-block', {
'icon--green': hasTracker,
'icon--disabled': !hasTracker,
'my-3': isDetailed,

View File

@@ -49,12 +49,6 @@
padding-top: 1rem;
}
@media (max-width: 1024px) {
.grid .key-colon, .grid .title--border {
font-weight: 600;
}
}
@media (max-width: 767.98px) {
.grid {
grid-template-columns: 35% 55%;
@@ -76,6 +70,10 @@
grid-column: 2 / span 1;
margin: 0 !important;
}
.grid .key-colon, .grid .title--border {
font-weight: 600;
}
}
.grid .key-colon:nth-child(odd)::after {

View File

@@ -97,7 +97,7 @@ const ResponseCell = ({
return (
<div className="logs__cell logs__cell--response" role="gridcell">
<IconTooltip
className={classNames('icons mr-4 icon--24 icon--lightgray logs__question', { 'my-3': isDetailed })}
className={classNames('icons mr-4 icon--24 icon--lightgray', { 'my-3': isDetailed })}
columnClass='grid grid--limited'
tooltipClass='px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details'
contentItemClass='text-truncate key-colon o-hidden'

View File

@@ -485,13 +485,3 @@
.bg--green {
color: var(--green79);
}
@media (max-width: 1024px) {
.logs__question {
display: none;
}
}
.logs__modal {
max-width: 720px;
}

View File

@@ -184,34 +184,27 @@ const Logs = () => {
setButtonType={setButtonType}
setModalOpened={setModalOpened}
/>
<Modal
portalClassName='grid'
isOpen={isSmallScreen && isModalOpened}
onRequestClose={closeModal}
style={{
content: {
width: '100%',
height: 'fit-content',
left: '50%',
top: 47,
padding: '1rem 1.5rem 1rem',
maxWidth: '720px',
transform: 'translateX(-50%)',
},
overlay: {
backgroundColor: 'rgba(0,0,0,0.5)',
},
}}
<Modal portalClassName='grid' isOpen={isSmallScreen && isModalOpened}
onRequestClose={closeModal}
style={{
content: {
width: '100%',
height: 'fit-content',
left: 0,
top: 47,
padding: '1rem 1.5rem 1rem',
},
overlay: {
backgroundColor: 'rgba(0,0,0,0.5)',
},
}}
>
<div className="logs__modal-wrap">
<svg
className="icon icon--24 icon-cross d-block cursor--pointer"
onClick={closeModal}
>
<use xlinkHref="#cross" />
</svg>
{processContent(detailedDataCurrent, buttonType)}
</div>
<svg
className="icon icon--24 icon-cross d-block d-md-none cursor--pointer"
onClick={closeModal}>
<use xlinkHref="#cross" />
</svg>
{processContent(detailedDataCurrent, buttonType)}
</Modal>
</>;

View File

@@ -9,12 +9,6 @@ import (
"github.com/AdguardTeam/golibs/log"
)
// HTTP scheme constants.
const (
SchemeHTTP = "http"
SchemeHTTPS = "https"
)
// RegisterFunc is the function that sets the handler to handle the URL for the
// method.
//

View File

@@ -18,18 +18,27 @@ import (
// How to test on a real Linux machine:
//
// 1. Run "sudo ipset create example_set hash:ip family ipv4".
// 1. Run:
//
// 2. Run "sudo ipset list example_set". The Members field should be empty.
// sudo ipset create example_set hash:ip family ipv4
//
// 3. Add the line "example.com/example_set" to your AdGuardHome.yaml.
// 2. Run:
//
// 4. Start AdGuardHome.
// sudo ipset list example_set
//
// 5. Make requests to example.com and its subdomains.
// The Members field should be empty.
//
// 6. Run "sudo ipset list example_set". The Members field should contain the
// resolved IP addresses.
// 3. Add the line "example.com/example_set" to your AdGuardHome.yaml.
//
// 4. Start AdGuardHome.
//
// 5. Make requests to example.com and its subdomains.
//
// 6. Run:
//
// sudo ipset list example_set
//
// The Members field should contain the resolved IP addresses.
// newIpsetMgr returns a new Linux ipset manager.
func newIpsetMgr(ipsetConf []string) (set IpsetManager, err error) {

View File

@@ -7,7 +7,6 @@ import (
"fmt"
"io"
"net"
"net/netip"
"syscall"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
@@ -32,12 +31,6 @@ var (
// the IP being static is available.
const ErrNoStaticIPInfo errors.Error = "no information about static ip"
// IPv4Localhost returns 127.0.0.1, which returns true for [netip.Addr.Is4].
func IPv4Localhost() (ip netip.Addr) { return netip.AddrFrom4([4]byte{0: 127, 3: 1}) }
// IPv6Localhost returns ::1, which returns true for [netip.Addr.Is6].
func IPv6Localhost() (ip netip.Addr) { return netip.AddrFrom16([16]byte{15: 1}) }
// IfaceHasStaticIP checks if interface is configured to have static IP address.
// If it can't give a definitive answer, it returns false and an error for which
// errors.Is(err, ErrNoStaticIPInfo) is true.
@@ -54,31 +47,26 @@ func IfaceSetStaticIP(ifaceName string) (err error) {
//
// TODO(e.burkov): Investigate if the gateway address may be fetched in another
// way since not every machine has the software installed.
func GatewayIP(ifaceName string) (ip netip.Addr) {
func GatewayIP(ifaceName string) (ip net.IP) {
code, out, err := aghosRunCommand("ip", "route", "show", "dev", ifaceName)
if err != nil {
log.Debug("%s", err)
return ip
return nil
} else if code != 0 {
log.Debug("fetching gateway ip: unexpected exit code: %d", code)
return ip
return nil
}
fields := bytes.Fields(out)
// The meaningful "ip route" command output should contain the word
// "default" at first field and default gateway IP address at third field.
if len(fields) < 3 || string(fields[0]) != "default" {
return ip
return nil
}
ip, err = netip.ParseAddr(string(fields[2]))
if err != nil {
return netip.Addr{}
}
return ip
return net.ParseIP(string(fields[2]))
}
// CanBindPrivilegedPorts checks if current process can bind to privileged
@@ -90,9 +78,9 @@ func CanBindPrivilegedPorts() (can bool, err error) {
// NetInterface represents an entry of network interfaces map.
type NetInterface struct {
// Addresses are the network interface addresses.
Addresses []netip.Addr `json:"ip_addresses,omitempty"`
Addresses []net.IP `json:"ip_addresses,omitempty"`
// Subnets are the IP networks for this network interface.
Subnets []netip.Prefix `json:"-"`
Subnets []*net.IPNet `json:"-"`
Name string `json:"name"`
HardwareAddr net.HardwareAddr `json:"hardware_address"`
Flags net.Flags `json:"flags"`
@@ -113,78 +101,57 @@ func (iface NetInterface) MarshalJSON() ([]byte, error) {
})
}
func NetInterfaceFrom(iface *net.Interface) (niface *NetInterface, err error) {
niface = &NetInterface{
Name: iface.Name,
HardwareAddr: iface.HardwareAddr,
Flags: iface.Flags,
MTU: iface.MTU,
}
addrs, err := iface.Addrs()
if err != nil {
return nil, fmt.Errorf("failed to get addresses for interface %s: %w", iface.Name, err)
}
// Collect network interface addresses.
for _, addr := range addrs {
n, ok := addr.(*net.IPNet)
if !ok {
// Should be *net.IPNet, this is weird.
return nil, fmt.Errorf("expected %[2]s to be %[1]T, got %[2]T", n, addr)
} else if ip4 := n.IP.To4(); ip4 != nil {
n.IP = ip4
}
ip, ok := netip.AddrFromSlice(n.IP)
if !ok {
return nil, fmt.Errorf("bad address %s", n.IP)
}
if ip.IsLinkLocalUnicast() {
// Ignore link-local IPv4.
if ip.Is4() {
continue
}
ip = ip.WithZone(iface.Name)
}
ones, _ := n.Mask.Size()
p := netip.PrefixFrom(ip, ones)
niface.Addresses = append(niface.Addresses, ip)
niface.Subnets = append(niface.Subnets, p)
}
return niface, nil
}
// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and
// WEB only we do not return link-local addresses here.
//
// TODO(e.burkov): Can't properly test the function since it's nontrivial to
// substitute net.Interface.Addrs and the net.InterfaceAddrs can't be used.
func GetValidNetInterfacesForWeb() (nifaces []*NetInterface, err error) {
func GetValidNetInterfacesForWeb() (netIfaces []*NetInterface, err error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, fmt.Errorf("getting interfaces: %w", err)
return nil, fmt.Errorf("couldn't get interfaces: %w", err)
} else if len(ifaces) == 0 {
return nil, errors.Error("no legible interfaces")
return nil, errors.Error("couldn't find any legible interface")
}
for i := range ifaces {
var niface *NetInterface
niface, err = NetInterfaceFrom(&ifaces[i])
for _, iface := range ifaces {
var addrs []net.Addr
addrs, err = iface.Addrs()
if err != nil {
return nil, err
} else if len(niface.Addresses) != 0 {
// Discard interfaces with no addresses.
nifaces = append(nifaces, niface)
return nil, fmt.Errorf("failed to get addresses for interface %s: %w", iface.Name, err)
}
netIface := &NetInterface{
MTU: iface.MTU,
Name: iface.Name,
HardwareAddr: iface.HardwareAddr,
Flags: iface.Flags,
}
// Collect network interface addresses.
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
// Should be net.IPNet, this is weird.
return nil, fmt.Errorf("got %s that is not net.IPNet, it is %T", addr, addr)
}
// Ignore link-local.
if ipNet.IP.IsLinkLocalUnicast() {
continue
}
netIface.Addresses = append(netIface.Addresses, ipNet.IP)
netIface.Subnets = append(netIface.Subnets, ipNet)
}
// Discard interfaces with no addresses.
if len(netIface.Addresses) != 0 {
netIfaces = append(netIfaces, netIface)
}
}
return nifaces, nil
return netIfaces, nil
}
// InterfaceByIP returns the name of the interface bound to ip.
@@ -193,7 +160,7 @@ func GetValidNetInterfacesForWeb() (nifaces []*NetInterface, err error) {
// IP address can be shared by multiple interfaces in some configurations.
//
// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb.
func InterfaceByIP(ip netip.Addr) (ifaceName string) {
func InterfaceByIP(ip net.IP) (ifaceName string) {
ifaces, err := GetValidNetInterfacesForWeb()
if err != nil {
return ""
@@ -201,7 +168,7 @@ func InterfaceByIP(ip netip.Addr) (ifaceName string) {
for _, iface := range ifaces {
for _, addr := range iface.Addresses {
if ip == addr {
if ip.Equal(addr) {
return iface.Name
}
}
@@ -210,16 +177,15 @@ func InterfaceByIP(ip netip.Addr) (ifaceName string) {
return ""
}
// GetSubnet returns the subnet corresponding to the interface of zero prefix if
// GetSubnet returns pointer to net.IPNet for the specified interface or nil if
// the search fails.
//
// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb.
func GetSubnet(ifaceName string) (p netip.Prefix) {
func GetSubnet(ifaceName string) *net.IPNet {
netIfaces, err := GetValidNetInterfacesForWeb()
if err != nil {
log.Error("Could not get network interfaces info: %v", err)
return p
return nil
}
for _, netIface := range netIfaces {
@@ -228,14 +194,14 @@ func GetSubnet(ifaceName string) (p netip.Prefix) {
}
}
return p
return nil
}
// CheckPort checks if the port is available for binding. network is expected
// to be one of "udp" and "tcp".
func CheckPort(network string, ipp netip.AddrPort) (err error) {
func CheckPort(network string, ip net.IP, port int) (err error) {
var c io.Closer
addr := ipp.String()
addr := netutil.IPPort{IP: ip, Port: port}.String()
switch network {
case "tcp":
c, err = net.Listen(network, addr)

View File

@@ -6,7 +6,7 @@ import (
"bufio"
"fmt"
"io"
"net/netip"
"net"
"os"
"strings"
@@ -151,7 +151,7 @@ func findIfaceLine(s *bufio.Scanner, name string) (ok bool) {
// interface through dhcpcd.conf.
func ifaceSetStaticIP(ifaceName string) (err error) {
ipNet := GetSubnet(ifaceName)
if !ipNet.Addr().IsValid() {
if ipNet.IP == nil {
return errors.Error("can't get IP address")
}
@@ -174,7 +174,7 @@ func ifaceSetStaticIP(ifaceName string) (err error) {
// dhcpcdConfIface returns configuration lines for the dhcpdc.conf files that
// configure the interface to have a static IP.
func dhcpcdConfIface(ifaceName string, subnet netip.Prefix, gateway netip.Addr) (conf string) {
func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gwIP net.IP) (conf string) {
b := &strings.Builder{}
stringutil.WriteToBuilder(
b,
@@ -183,15 +183,15 @@ func dhcpcdConfIface(ifaceName string, subnet netip.Prefix, gateway netip.Addr)
" added by AdGuard Home.\ninterface ",
ifaceName,
"\nstatic ip_address=",
subnet.String(),
ipNet.String(),
"\n",
)
if gateway.IsValid() {
stringutil.WriteToBuilder(b, "static routers=", gateway.String(), "\n")
if gwIP != nil {
stringutil.WriteToBuilder(b, "static routers=", gwIP.String(), "\n")
}
stringutil.WriteToBuilder(b, "static domain_name_servers=", subnet.Addr().String(), "\n\n")
stringutil.WriteToBuilder(b, "static domain_name_servers=", ipNet.IP.String(), "\n\n")
return b.String()
}

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"io/fs"
"net"
"net/netip"
"os"
"strings"
"testing"
@@ -94,34 +93,34 @@ func TestGatewayIP(t *testing.T) {
const cmd = "ip route show dev " + ifaceName
testCases := []struct {
shell mapShell
want netip.Addr
name string
shell mapShell
want net.IP
}{{
shell: theOnlyCmd(cmd, 0, `default via 1.2.3.4 onlink`, nil),
want: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
name: "success_v4",
shell: theOnlyCmd(cmd, 0, `default via 1.2.3.4 onlink`, nil),
want: net.IP{1, 2, 3, 4}.To16(),
}, {
name: "success_v6",
shell: theOnlyCmd(cmd, 0, `default via ::ffff onlink`, nil),
want: netip.AddrFrom16([16]byte{
want: net.IP{
0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xFF, 0xFF,
}),
name: "success_v6",
},
}, {
shell: theOnlyCmd(cmd, 0, `non-default via 1.2.3.4 onlink`, nil),
want: netip.Addr{},
name: "bad_output",
shell: theOnlyCmd(cmd, 0, `non-default via 1.2.3.4 onlink`, nil),
want: nil,
}, {
shell: theOnlyCmd(cmd, 0, "", errors.Error("can't run command")),
want: netip.Addr{},
name: "err_runcmd",
shell: theOnlyCmd(cmd, 0, "", errors.Error("can't run command")),
want: nil,
}, {
shell: theOnlyCmd(cmd, 1, "", nil),
want: netip.Addr{},
name: "bad_code",
shell: theOnlyCmd(cmd, 1, "", nil),
want: nil,
}}
for _, tc := range testCases {
@@ -199,21 +198,17 @@ func TestBroadcastFromIPNet(t *testing.T) {
}
func TestCheckPort(t *testing.T) {
laddr := netip.AddrPortFrom(IPv4Localhost(), 0)
t.Run("tcp_bound", func(t *testing.T) {
l, err := net.Listen("tcp", laddr.String())
l, err := net.Listen("tcp", "127.0.0.1:")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, l.Close)
addr := l.Addr()
require.IsType(t, new(net.TCPAddr), addr)
ipp := netutil.IPPortFromAddr(l.Addr())
require.NotNil(t, ipp)
require.NotNil(t, ipp.IP)
require.NotZero(t, ipp.Port)
ipp := addr.(*net.TCPAddr).AddrPort()
require.Equal(t, laddr.Addr(), ipp.Addr())
require.NotZero(t, ipp.Port())
err = CheckPort("tcp", ipp)
err = CheckPort("tcp", ipp.IP, ipp.Port)
target := &net.OpError{}
require.ErrorAs(t, err, &target)
@@ -221,18 +216,16 @@ func TestCheckPort(t *testing.T) {
})
t.Run("udp_bound", func(t *testing.T) {
conn, err := net.ListenPacket("udp", laddr.String())
conn, err := net.ListenPacket("udp", "127.0.0.1:")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, conn.Close)
addr := conn.LocalAddr()
require.IsType(t, new(net.UDPAddr), addr)
ipp := netutil.IPPortFromAddr(conn.LocalAddr())
require.NotNil(t, ipp)
require.NotNil(t, ipp.IP)
require.NotZero(t, ipp.Port)
ipp := addr.(*net.UDPAddr).AddrPort()
require.Equal(t, laddr.Addr(), ipp.Addr())
require.NotZero(t, ipp.Port())
err = CheckPort("udp", ipp)
err = CheckPort("udp", ipp.IP, ipp.Port)
target := &net.OpError{}
require.ErrorAs(t, err, &target)
@@ -240,12 +233,12 @@ func TestCheckPort(t *testing.T) {
})
t.Run("bad_network", func(t *testing.T) {
err := CheckPort("bad_network", netip.AddrPortFrom(netip.Addr{}, 0))
err := CheckPort("bad_network", nil, 0)
assert.NoError(t, err)
})
t.Run("can_bind", func(t *testing.T) {
err := CheckPort("udp", netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
err := CheckPort("udp", net.IP{0, 0, 0, 0}, 0)
assert.NoError(t, err)
})
}
@@ -329,18 +322,18 @@ func TestNetInterface_MarshalJSON(t *testing.T) {
`"mtu":1500` +
`}` + "\n"
ip4, ok := netip.AddrFromSlice([]byte{1, 2, 3, 4})
require.True(t, ok)
ip6, ok := netip.AddrFromSlice([]byte{0xAA, 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
require.True(t, ok)
net4 := netip.PrefixFrom(ip4, 24)
net6 := netip.PrefixFrom(ip6, 8)
ip4, ip6 := net.IP{1, 2, 3, 4}, net.IP{0xAA, 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
mask4, mask6 := net.CIDRMask(24, netutil.IPv4BitLen), net.CIDRMask(8, netutil.IPv6BitLen)
iface := &NetInterface{
Addresses: []netip.Addr{ip4, ip6},
Subnets: []netip.Prefix{net4, net6},
Addresses: []net.IP{ip4, ip6},
Subnets: []*net.IPNet{{
IP: ip4.Mask(mask4),
Mask: mask4,
}, {
IP: ip6.Mask(mask6),
Mask: mask6,
}},
Name: "iface0",
HardwareAddr: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
Flags: net.FlagUp | net.FlagMulticast,

View File

@@ -349,8 +349,6 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
return
}
// ignore link-local
//
// TODO(e.burkov): Try to listen DHCP on LLA as well.
if ipnet.IP.IsLinkLocalUnicast() {
continue
}
@@ -361,7 +359,7 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
}
}
if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 {
jsonIface.GatewayIP = aghnet.GatewayIP(iface.Name).AsSlice()
jsonIface.GatewayIP = aghnet.GatewayIP(iface.Name)
response[iface.Name] = jsonIface
}
}

View File

@@ -296,7 +296,7 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
values := []dns.SVCBKeyValue{
&dns.SVCBAlpn{Alpn: []string{"h2"}},
&dns.SVCBPort{Port: uint16(addr.Port)},
&dns.SVCBDoHPath{Template: "/dns-query{?dns}"},
&dns.SVCBDoHPath{Template: "/dns-query?dns"},
}
ans := &dns.SVCB{

View File

@@ -26,7 +26,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
Value: []dns.SVCBKeyValue{
&dns.SVCBAlpn{Alpn: []string{"h2"}},
&dns.SVCBPort{Port: 8044},
&dns.SVCBDoHPath{Template: "/dns-query{?dns}"},
&dns.SVCBDoHPath{Template: "/dns-query?dns"},
},
}

View File

@@ -67,11 +67,10 @@ func createTestServer(
ID: 0, Data: []byte(rules),
}}
f, err := filtering.New(filterConf, filters)
require.NoError(t, err)
f := filtering.New(filterConf, filters)
f.SetEnabled(true)
var err error
s, err = NewServer(DNSCreateParams{
DHCPServer: testDHCP,
DNSFilter: f,
@@ -775,9 +774,7 @@ func TestBlockedCustomIP(t *testing.T) {
Data: []byte(rules),
}}
f, err := filtering.New(&filtering.Config{}, filters)
require.NoError(t, err)
f := filtering.New(&filtering.Config{}, filters)
s, err := NewServer(DNSCreateParams{
DHCPServer: testDHCP,
DNSFilter: f,
@@ -909,9 +906,7 @@ func TestRewrite(t *testing.T) {
Type: dns.TypeCNAME,
}},
}
f, err := filtering.New(c, nil)
require.NoError(t, err)
f := filtering.New(c, nil)
f.SetEnabled(true)
s, err := NewServer(DNSCreateParams{
@@ -1026,14 +1021,19 @@ var testDHCP = &dhcpd.MockInterface{
OnWriteDiskConfig: func(c *dhcpd.ServerConfig) { panic("not implemented") },
}
// func (*testDHCP) Leases(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) {
// return []*dhcpd.Lease{{
// IP: net.IP{192, 168, 12, 34},
// HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
// Hostname: "myhost",
// }}
// }
func TestPTRResponseFromDHCPLeases(t *testing.T) {
const localDomain = "lan"
flt, err := filtering.New(&filtering.Config{}, nil)
require.NoError(t, err)
s, err := NewServer(DNSCreateParams{
DNSFilter: flt,
DNSFilter: filtering.New(&filtering.Config{}, nil),
DHCPServer: testDHCP,
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
LocalDomain: localDomain,
@@ -1100,11 +1100,9 @@ func TestPTRResponseFromHosts(t *testing.T) {
assert.Equal(t, uint32(1), atomic.LoadUint32(&eventsCalledCounter))
})
flt, err := filtering.New(&filtering.Config{
flt := filtering.New(&filtering.Config{
EtcHosts: hc,
}, nil)
require.NoError(t, err)
flt.SetEnabled(true)
var s *Server

View File

@@ -35,8 +35,7 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
ID: 0, Data: []byte(rules),
}}
f, err := filtering.New(&filtering.Config{}, filters)
require.NoError(t, err)
f := filtering.New(&filtering.Config{}, filters)
f.SetEnabled(true)
s, err := NewServer(DNSCreateParams{

View File

@@ -421,34 +421,31 @@ func initBlockedServices() {
}
// BlockedSvcKnown - return TRUE if a blocked service name is known
func BlockedSvcKnown(s string) (ok bool) {
_, ok = serviceRules[s]
func BlockedSvcKnown(s string) bool {
_, ok := serviceRules[s]
return ok
}
// ApplyBlockedServices - set blocked services settings for this DNS request
func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string) {
func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string, global bool) {
setts.ServicesRules = []ServiceEntry{}
if list == nil {
if global {
d.confLock.RLock()
defer d.confLock.RUnlock()
list = d.Config.BlockedServices
}
for _, name := range list {
rules, ok := serviceRules[name]
if !ok {
log.Error("unknown service name: %s", name)
continue
}
setts.ServicesRules = append(setts.ServicesRules, ServiceEntry{
Name: name,
Rules: rules,
})
s := ServiceEntry{}
s.Name = name
s.Rules = rules
setts.ServicesRules = append(setts.ServicesRules, s)
}
}
@@ -493,3 +490,10 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
d.ConfigModified()
}
// registerBlockedServicesHandlers - register HTTP handlers
func (d *DNSFilter) registerBlockedServicesHandlers() {
d.Config.HTTPRegister(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesAvailableServices)
d.Config.HTTPRegister(http.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList)
d.Config.HTTPRegister(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet)
}

View File

@@ -49,7 +49,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
|1.2.3.5.in-addr.arpa^$dnsrewrite=NOERROR;PTR;new-ptr-with-dot.
`
f, _ := newForTest(t, nil, []Filter{{ID: 0, Data: []byte(text)}})
f := newForTest(t, nil, []Filter{{ID: 0, Data: []byte(text)}})
setts := &Settings{
FilteringEnabled: true,
}

View File

@@ -6,10 +6,7 @@ import (
"fmt"
"io/fs"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"runtime"
"runtime/debug"
"strings"
@@ -27,7 +24,6 @@ import (
"github.com/AdguardTeam/urlfilter/filterlist"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// The IDs of built-in filter lists.
@@ -73,13 +69,8 @@ type Config struct {
// enabled is used to be returned within Settings.
//
// It is of type uint32 to be accessed by atomic.
//
// TODO(e.burkov): Use atomic.Bool in Go 1.19.
enabled uint32
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours)
ParentalEnabled bool `yaml:"parental_enabled"`
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
@@ -107,24 +98,6 @@ type Config struct {
// CustomResolver is the resolver used by DNSFilter.
CustomResolver Resolver `yaml:"-"`
// HTTPClient is the client to use for updating the remote filters.
HTTPClient *http.Client `yaml:"-"`
// DataDir is used to store filters' contents.
DataDir string `yaml:"-"`
// filtersMu protects filter lists.
filtersMu *sync.RWMutex
// Filters are the blocking filter lists.
Filters []FilterYAML `yaml:"-"`
// WhitelistFilters are the allowing filter lists.
WhitelistFilters []FilterYAML `yaml:"-"`
// UserRules is the global list of custom rules.
UserRules []string `yaml:"-"`
}
// LookupStats store stats collected during safebrowsing or parental checks
@@ -155,13 +128,11 @@ type hostChecker struct {
// DNSFilter matches hostnames and DNS requests against filtering rules.
type DNSFilter struct {
rulesStorage *filterlist.RuleStorage
filteringEngine *urlfilter.DNSEngine
rulesStorage *filterlist.RuleStorage
filteringEngine *urlfilter.DNSEngine
rulesStorageAllow *filterlist.RuleStorage
filteringEngineAllow *urlfilter.DNSEngine
engineLock sync.RWMutex
engineLock sync.RWMutex
parentalServer string // access via methods
safeBrowsingServer string // access via methods
@@ -185,12 +156,6 @@ type DNSFilter struct {
// TODO(e.burkov): Use upstream that configured in dnsforward instead.
resolver Resolver
refreshLock *sync.Mutex
// filterTitleRegexp is the regular expression to retrieve a name of a
// filter list.
filterTitleRegexp *regexp.Regexp
hostCheckers []hostChecker
}
@@ -203,7 +168,7 @@ type Filter struct {
Data []byte `yaml:"-"`
// ID is automatically assigned when filter is added using nextFilterID.
ID int64 `yaml:"id"`
ID int64
}
// Reason holds an enum detailing why it was filtered or not filtered
@@ -280,7 +245,15 @@ func (r Reason) String() string {
}
// In returns true if reasons include r.
func (r Reason) In(reasons ...Reason) (ok bool) { return slices.Contains(reasons, r) }
func (r Reason) In(reasons ...Reason) (ok bool) {
for _, reason := range reasons {
if r == reason {
return true
}
}
return false
}
// SetEnabled sets the status of the *DNSFilter.
func (d *DNSFilter) SetEnabled(enabled bool) {
@@ -288,7 +261,6 @@ func (d *DNSFilter) SetEnabled(enabled bool) {
if enabled {
i = 1
}
atomic.StoreUint32(&d.enabled, uint32(i))
}
@@ -307,20 +279,11 @@ func (d *DNSFilter) GetConfig() (s Settings) {
// WriteDiskConfig - write configuration
func (d *DNSFilter) WriteDiskConfig(c *Config) {
func() {
d.confLock.Lock()
defer d.confLock.Unlock()
d.confLock.Lock()
defer d.confLock.Unlock()
*c = d.Config
c.Rewrites = cloneRewrites(c.Rewrites)
}()
d.filtersMu.RLock()
defer d.filtersMu.RUnlock()
c.Filters = slices.Clone(d.Filters)
c.WhitelistFilters = slices.Clone(d.WhitelistFilters)
c.UserRules = slices.Clone(d.UserRules)
*c = d.Config
c.Rewrites = cloneRewrites(c.Rewrites)
}
// cloneRewrites returns a deep copy of entries.
@@ -346,8 +309,6 @@ func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool)
}
d.filtersInitializerLock.Lock() // prevent multiple writers from adding more than 1 task
defer d.filtersInitializerLock.Unlock()
// remove all pending tasks
stop := false
for !stop {
@@ -360,6 +321,7 @@ func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool)
}
d.filtersInitializerChan <- params
d.filtersInitializerLock.Unlock()
return nil
}
@@ -388,19 +350,22 @@ func (d *DNSFilter) filtersInitializer() {
func (d *DNSFilter) Close() {
d.engineLock.Lock()
defer d.engineLock.Unlock()
d.reset()
}
func (d *DNSFilter) reset() {
var err error
if d.rulesStorage != nil {
if err := d.rulesStorage.Close(); err != nil {
err = d.rulesStorage.Close()
if err != nil {
log.Error("filtering: rulesStorage.Close: %s", err)
}
}
if d.rulesStorageAllow != nil {
if err := d.rulesStorageAllow.Close(); err != nil {
err = d.rulesStorageAllow.Close()
if err != nil {
log.Error("filtering: rulesStorageAllow.Close: %s", err)
}
}
@@ -920,30 +885,29 @@ func InitModule() {
initBlockedServices()
}
// New creates properly initialized DNS Filter that is ready to be used. c must
// be non-nil.
func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
// New creates properly initialized DNS Filter that is ready to be used.
func New(c *Config, blockFilters []Filter) (d *DNSFilter) {
d = &DNSFilter{
resolver: net.DefaultResolver,
refreshLock: &sync.Mutex{},
filterTitleRegexp: regexp.MustCompile(`^! Title: +(.*)$`),
resolver: net.DefaultResolver,
}
if c != nil {
d.safebrowsingCache = cache.New(cache.Config{
EnableLRU: true,
MaxSize: c.SafeBrowsingCacheSize,
})
d.safeSearchCache = cache.New(cache.Config{
EnableLRU: true,
MaxSize: c.SafeSearchCacheSize,
})
d.parentalCache = cache.New(cache.Config{
EnableLRU: true,
MaxSize: c.ParentalCacheSize,
})
d.safebrowsingCache = cache.New(cache.Config{
EnableLRU: true,
MaxSize: c.SafeBrowsingCacheSize,
})
d.safeSearchCache = cache.New(cache.Config{
EnableLRU: true,
MaxSize: c.SafeSearchCacheSize,
})
d.parentalCache = cache.New(cache.Config{
EnableLRU: true,
MaxSize: c.ParentalCacheSize,
})
if r := c.CustomResolver; r != nil {
d.resolver = r
if c.CustomResolver != nil {
d.resolver = c.CustomResolver
}
}
d.hostCheckers = []hostChecker{{
@@ -966,26 +930,27 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
name: "safe search",
}}
defer func() { err = errors.Annotate(err, "filtering: %w") }()
err = d.initSecurityServices()
err := d.initSecurityServices()
if err != nil {
return nil, fmt.Errorf("initializing services: %s", err)
log.Error("filtering: initialize services: %s", err)
return nil
}
d.Config = *c
d.filtersMu = &sync.RWMutex{}
if c != nil {
d.Config = *c
err = d.prepareRewrites()
if err != nil {
log.Error("rewrites: preparing: %s", err)
err = d.prepareRewrites()
if err != nil {
return nil, fmt.Errorf("rewrites: preparing: %s", err)
return nil
}
}
bsvcs := []string{}
for _, s := range d.BlockedServices {
if !BlockedSvcKnown(s) {
log.Debug("skipping unknown blocked-service %q", s)
continue
}
bsvcs = append(bsvcs, s)
@@ -995,24 +960,13 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
if blockFilters != nil {
err = d.initFiltering(nil, blockFilters)
if err != nil {
log.Error("Can't initialize filtering subsystem: %s", err)
d.Close()
return nil, fmt.Errorf("initializing filtering subsystem: %s", err)
return nil
}
}
_ = os.MkdirAll(filepath.Join(d.DataDir, filterDir), 0o755)
d.loadFilters(d.Filters)
d.loadFilters(d.WhitelistFilters)
d.Filters = deduplicateFilters(d.Filters)
d.WhitelistFilters = deduplicateFilters(d.WhitelistFilters)
updateUniqueFilterID(d.Filters)
updateUniqueFilterID(d.WhitelistFilters)
return d, nil
return d
}
// Start - start the module:
@@ -1022,10 +976,9 @@ func (d *DNSFilter) Start() {
d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
go d.filtersInitializer()
d.RegisterFilteringHandlers()
// Here we should start updating filters,
// but currently we can't wake up the periodic task to do so.
// So for now we just start this periodic task from here.
go d.periodicallyRefreshFilters()
if d.Config.HTTPRegister != nil { // for tests
d.registerSecurityHandlers()
d.registerRewritesHandlers()
d.registerBlockedServicesHandlers()
}
}

View File

@@ -26,6 +26,10 @@ const (
pcBlocked = "pornhub.com"
)
var setts = Settings{
ProtectionEnabled: true,
}
// Helpers.
func purgeCaches(d *DNSFilter) {
@@ -40,8 +44,8 @@ func purgeCaches(d *DNSFilter) {
}
}
func newForTest(t testing.TB, c *Config, filters []Filter) (f *DNSFilter, setts *Settings) {
setts = &Settings{
func newForTest(t testing.TB, c *Config, filters []Filter) *DNSFilter {
setts = Settings{
ProtectionEnabled: true,
FilteringEnabled: true,
}
@@ -53,31 +57,26 @@ func newForTest(t testing.TB, c *Config, filters []Filter) (f *DNSFilter, setts
setts.SafeSearchEnabled = c.SafeSearchEnabled
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
setts.ParentalEnabled = c.ParentalEnabled
} else {
// It must not be nil.
c = &Config{}
}
f, err := New(c, filters)
require.NoError(t, err)
d := New(c, filters)
purgeCaches(d)
purgeCaches(f)
return f, setts
return d
}
func (d *DNSFilter) checkMatch(t *testing.T, hostname string, setts *Settings) {
func (d *DNSFilter) checkMatch(t *testing.T, hostname string) {
t.Helper()
res, err := d.CheckHost(hostname, dns.TypeA, setts)
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
require.NoErrorf(t, err, "host %q", hostname)
assert.Truef(t, res.IsFiltered, "host %q", hostname)
}
func (d *DNSFilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16, setts *Settings) {
func (d *DNSFilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) {
t.Helper()
res, err := d.CheckHost(hostname, qtype, setts)
res, err := d.CheckHost(hostname, qtype, &setts)
require.NoErrorf(t, err, "host %q", hostname, err)
require.NotEmpty(t, res.Rules, "host %q", hostname)
@@ -89,10 +88,10 @@ func (d *DNSFilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16
assert.Equalf(t, ip, r.IP.String(), "host %q", hostname)
}
func (d *DNSFilter) checkMatchEmpty(t *testing.T, hostname string, setts *Settings) {
func (d *DNSFilter) checkMatchEmpty(t *testing.T, hostname string) {
t.Helper()
res, err := d.CheckHost(hostname, dns.TypeA, setts)
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
require.NoErrorf(t, err, "host %q", hostname)
assert.Falsef(t, res.IsFiltered, "host %q", hostname)
@@ -112,19 +111,19 @@ func TestEtcHostsMatching(t *testing.T) {
filters := []Filter{{
ID: 0, Data: []byte(text),
}}
d, setts := newForTest(t, nil, filters)
d := newForTest(t, nil, filters)
t.Cleanup(d.Close)
d.checkMatchIP(t, "google.com", addr, dns.TypeA, setts)
d.checkMatchIP(t, "www.google.com", addr, dns.TypeA, setts)
d.checkMatchEmpty(t, "subdomain.google.com", setts)
d.checkMatchEmpty(t, "example.org", setts)
d.checkMatchIP(t, "google.com", addr, dns.TypeA)
d.checkMatchIP(t, "www.google.com", addr, dns.TypeA)
d.checkMatchEmpty(t, "subdomain.google.com")
d.checkMatchEmpty(t, "example.org")
// IPv4 match.
d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA, setts)
d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA)
// Empty IPv6.
res, err := d.CheckHost("block.com", dns.TypeAAAA, setts)
res, err := d.CheckHost("block.com", dns.TypeAAAA, &setts)
require.NoError(t, err)
assert.True(t, res.IsFiltered)
@@ -135,10 +134,10 @@ func TestEtcHostsMatching(t *testing.T) {
assert.Empty(t, res.Rules[0].IP)
// IPv6 match.
d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA, setts)
d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA)
// Empty IPv4.
res, err = d.CheckHost("ipv6.com", dns.TypeA, setts)
res, err = d.CheckHost("ipv6.com", dns.TypeA, &setts)
require.NoError(t, err)
assert.True(t, res.IsFiltered)
@@ -149,7 +148,7 @@ func TestEtcHostsMatching(t *testing.T) {
assert.Empty(t, res.Rules[0].IP)
// Two IPv4, both must be returned.
res, err = d.CheckHost("host2", dns.TypeA, setts)
res, err = d.CheckHost("host2", dns.TypeA, &setts)
require.NoError(t, err)
assert.True(t, res.IsFiltered)
@@ -160,7 +159,7 @@ func TestEtcHostsMatching(t *testing.T) {
assert.Equal(t, res.Rules[1].IP, net.IP{0, 0, 0, 2})
// One IPv6 address.
res, err = d.CheckHost("host2", dns.TypeAAAA, setts)
res, err = d.CheckHost("host2", dns.TypeAAAA, &setts)
require.NoError(t, err)
assert.True(t, res.IsFiltered)
@@ -177,27 +176,27 @@ func TestSafeBrowsing(t *testing.T) {
aghtest.ReplaceLogWriter(t, logOutput)
aghtest.ReplaceLogLevel(t, log.DEBUG)
d, setts := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
t.Cleanup(d.Close)
d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
d.checkMatch(t, sbBlocked, setts)
d.checkMatch(t, sbBlocked)
require.Contains(t, logOutput.String(), fmt.Sprintf("safebrowsing lookup for %q", sbBlocked))
d.checkMatch(t, "test."+sbBlocked, setts)
d.checkMatchEmpty(t, "yandex.ru", setts)
d.checkMatchEmpty(t, pcBlocked, setts)
d.checkMatch(t, "test."+sbBlocked)
d.checkMatchEmpty(t, "yandex.ru")
d.checkMatchEmpty(t, pcBlocked)
// Cached result.
d.safeBrowsingServer = "127.0.0.1"
d.checkMatch(t, sbBlocked, setts)
d.checkMatchEmpty(t, pcBlocked, setts)
d.checkMatch(t, sbBlocked)
d.checkMatchEmpty(t, pcBlocked)
d.safeBrowsingServer = defaultSafebrowsingServer
}
func TestParallelSB(t *testing.T) {
d, setts := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
t.Cleanup(d.Close)
d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
@@ -206,10 +205,10 @@ func TestParallelSB(t *testing.T) {
for i := 0; i < 100; i++ {
t.Run(fmt.Sprintf("aaa%d", i), func(t *testing.T) {
t.Parallel()
d.checkMatch(t, sbBlocked, setts)
d.checkMatch(t, "test."+sbBlocked, setts)
d.checkMatchEmpty(t, "yandex.ru", setts)
d.checkMatchEmpty(t, pcBlocked, setts)
d.checkMatch(t, sbBlocked)
d.checkMatch(t, "test."+sbBlocked)
d.checkMatchEmpty(t, "yandex.ru")
d.checkMatchEmpty(t, pcBlocked)
})
}
})
@@ -218,7 +217,7 @@ func TestParallelSB(t *testing.T) {
// Safe Search.
func TestSafeSearch(t *testing.T) {
d, _ := newForTest(t, &Config{SafeSearchEnabled: true}, nil)
d := newForTest(t, &Config{SafeSearchEnabled: true}, nil)
t.Cleanup(d.Close)
val, ok := d.SafeSearchDomain("www.google.com")
require.True(t, ok)
@@ -227,7 +226,7 @@ func TestSafeSearch(t *testing.T) {
}
func TestCheckHostSafeSearchYandex(t *testing.T) {
d, setts := newForTest(t, &Config{
d := newForTest(t, &Config{
SafeSearchEnabled: true,
}, nil)
t.Cleanup(d.Close)
@@ -244,7 +243,7 @@ func TestCheckHostSafeSearchYandex(t *testing.T) {
"www.yandex.com",
} {
t.Run(strings.ToLower(host), func(t *testing.T) {
res, err := d.CheckHost(host, dns.TypeA, setts)
res, err := d.CheckHost(host, dns.TypeA, &setts)
require.NoError(t, err)
assert.True(t, res.IsFiltered)
@@ -259,7 +258,7 @@ func TestCheckHostSafeSearchYandex(t *testing.T) {
func TestCheckHostSafeSearchGoogle(t *testing.T) {
resolver := &aghtest.TestResolver{}
d, setts := newForTest(t, &Config{
d := newForTest(t, &Config{
SafeSearchEnabled: true,
CustomResolver: resolver,
}, nil)
@@ -278,7 +277,7 @@ func TestCheckHostSafeSearchGoogle(t *testing.T) {
"www.google.je",
} {
t.Run(host, func(t *testing.T) {
res, err := d.CheckHost(host, dns.TypeA, setts)
res, err := d.CheckHost(host, dns.TypeA, &setts)
require.NoError(t, err)
assert.True(t, res.IsFiltered)
@@ -292,12 +291,12 @@ func TestCheckHostSafeSearchGoogle(t *testing.T) {
}
func TestSafeSearchCacheYandex(t *testing.T) {
d, setts := newForTest(t, nil, nil)
d := newForTest(t, nil, nil)
t.Cleanup(d.Close)
const domain = "yandex.ru"
// Check host with disabled safesearch.
res, err := d.CheckHost(domain, dns.TypeA, setts)
res, err := d.CheckHost(domain, dns.TypeA, &setts)
require.NoError(t, err)
assert.False(t, res.IsFiltered)
@@ -306,10 +305,10 @@ func TestSafeSearchCacheYandex(t *testing.T) {
yandexIP := net.IPv4(213, 180, 193, 56)
d, setts = newForTest(t, &Config{SafeSearchEnabled: true}, nil)
d = newForTest(t, &Config{SafeSearchEnabled: true}, nil)
t.Cleanup(d.Close)
res, err = d.CheckHost(domain, dns.TypeA, setts)
res, err = d.CheckHost(domain, dns.TypeA, &setts)
require.NoError(t, err)
// For yandex we already know valid IP.
@@ -326,20 +325,20 @@ func TestSafeSearchCacheYandex(t *testing.T) {
func TestSafeSearchCacheGoogle(t *testing.T) {
resolver := &aghtest.TestResolver{}
d, setts := newForTest(t, &Config{
d := newForTest(t, &Config{
CustomResolver: resolver,
}, nil)
t.Cleanup(d.Close)
const domain = "www.google.ru"
res, err := d.CheckHost(domain, dns.TypeA, setts)
res, err := d.CheckHost(domain, dns.TypeA, &setts)
require.NoError(t, err)
assert.False(t, res.IsFiltered)
require.Empty(t, res.Rules)
d, setts = newForTest(t, &Config{SafeSearchEnabled: true}, nil)
d = newForTest(t, &Config{SafeSearchEnabled: true}, nil)
t.Cleanup(d.Close)
d.resolver = resolver
@@ -359,7 +358,7 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
}
}
res, err = d.CheckHost(domain, dns.TypeA, setts)
res, err = d.CheckHost(domain, dns.TypeA, &setts)
require.NoError(t, err)
require.Len(t, res.Rules, 1)
@@ -380,22 +379,22 @@ func TestParentalControl(t *testing.T) {
aghtest.ReplaceLogWriter(t, logOutput)
aghtest.ReplaceLogLevel(t, log.DEBUG)
d, setts := newForTest(t, &Config{ParentalEnabled: true}, nil)
d := newForTest(t, &Config{ParentalEnabled: true}, nil)
t.Cleanup(d.Close)
d.SetParentalUpstream(aghtest.NewBlockUpstream(pcBlocked, true))
d.checkMatch(t, pcBlocked, setts)
d.checkMatch(t, pcBlocked)
require.Contains(t, logOutput.String(), fmt.Sprintf("parental lookup for %q", pcBlocked))
d.checkMatch(t, "www."+pcBlocked, setts)
d.checkMatchEmpty(t, "www.yandex.ru", setts)
d.checkMatchEmpty(t, "yandex.ru", setts)
d.checkMatchEmpty(t, "api.jquery.com", setts)
d.checkMatch(t, "www."+pcBlocked)
d.checkMatchEmpty(t, "www.yandex.ru")
d.checkMatchEmpty(t, "yandex.ru")
d.checkMatchEmpty(t, "api.jquery.com")
// Test cached result.
d.parentalServer = "127.0.0.1"
d.checkMatch(t, pcBlocked, setts)
d.checkMatchEmpty(t, "yandex.ru", setts)
d.checkMatch(t, pcBlocked)
d.checkMatchEmpty(t, "yandex.ru")
}
// Filtering.
@@ -680,10 +679,10 @@ func TestMatching(t *testing.T) {
for _, tc := range testCases {
t.Run(fmt.Sprintf("%s-%s", tc.name, tc.host), func(t *testing.T) {
filters := []Filter{{ID: 0, Data: []byte(tc.rules)}}
d, setts := newForTest(t, nil, filters)
d := newForTest(t, nil, filters)
t.Cleanup(d.Close)
res, err := d.CheckHost(tc.host, tc.wantDNSType, setts)
res, err := d.CheckHost(tc.host, tc.wantDNSType, &setts)
require.NoError(t, err)
assert.Equalf(t, tc.wantIsFiltered, res.IsFiltered, "Hostname %s has wrong result (%v must be %v)", tc.host, res.IsFiltered, tc.wantIsFiltered)
@@ -706,7 +705,7 @@ func TestWhitelist(t *testing.T) {
whiteFilters := []Filter{{
ID: 0, Data: []byte(whiteRules),
}}
d, setts := newForTest(t, nil, filters)
d := newForTest(t, nil, filters)
err := d.SetFilters(filters, whiteFilters, false)
require.NoError(t, err)
@@ -714,7 +713,7 @@ func TestWhitelist(t *testing.T) {
t.Cleanup(d.Close)
// Matched by white filter.
res, err := d.CheckHost("host1", dns.TypeA, setts)
res, err := d.CheckHost("host1", dns.TypeA, &setts)
require.NoError(t, err)
assert.False(t, res.IsFiltered)
@@ -725,7 +724,7 @@ func TestWhitelist(t *testing.T) {
assert.Equal(t, "||host1^", res.Rules[0].Text)
// Not matched by white filter, but matched by block filter.
res, err = d.CheckHost("host2", dns.TypeA, setts)
res, err = d.CheckHost("host2", dns.TypeA, &setts)
require.NoError(t, err)
assert.True(t, res.IsFiltered)
@@ -751,7 +750,7 @@ func applyClientSettings(setts *Settings) {
}
func TestClientSettings(t *testing.T) {
d, setts := newForTest(t,
d := newForTest(t,
&Config{
ParentalEnabled: true,
SafeBrowsingEnabled: false,
@@ -797,7 +796,7 @@ func TestClientSettings(t *testing.T) {
return func(t *testing.T) {
t.Helper()
r, err := d.CheckHost(tc.host, dns.TypeA, setts)
r, err := d.CheckHost(tc.host, dns.TypeA, &setts)
require.NoError(t, err)
if before {
@@ -815,7 +814,7 @@ func TestClientSettings(t *testing.T) {
t.Run(tc.name, makeTester(tc, tc.before))
}
applyClientSettings(setts)
applyClientSettings(&setts)
for _, tc := range testCases {
t.Run(tc.name, makeTester(tc, !tc.before))
@@ -825,13 +824,13 @@ func TestClientSettings(t *testing.T) {
// Benchmarks.
func BenchmarkSafeBrowsing(b *testing.B) {
d, setts := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil)
d := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil)
b.Cleanup(d.Close)
d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
for n := 0; n < b.N; n++ {
res, err := d.CheckHost(sbBlocked, dns.TypeA, setts)
res, err := d.CheckHost(sbBlocked, dns.TypeA, &setts)
require.NoError(b, err)
assert.Truef(b, res.IsFiltered, "expected hostname %q to match", sbBlocked)
@@ -839,14 +838,14 @@ func BenchmarkSafeBrowsing(b *testing.B) {
}
func BenchmarkSafeBrowsingParallel(b *testing.B) {
d, setts := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil)
d := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil)
b.Cleanup(d.Close)
d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
res, err := d.CheckHost(sbBlocked, dns.TypeA, setts)
res, err := d.CheckHost(sbBlocked, dns.TypeA, &setts)
require.NoError(b, err)
assert.Truef(b, res.IsFiltered, "expected hostname %q to match", sbBlocked)
@@ -855,7 +854,7 @@ func BenchmarkSafeBrowsingParallel(b *testing.B) {
}
func BenchmarkSafeSearch(b *testing.B) {
d, _ := newForTest(b, &Config{SafeSearchEnabled: true}, nil)
d := newForTest(b, &Config{SafeSearchEnabled: true}, nil)
b.Cleanup(d.Close)
for n := 0; n < b.N; n++ {
val, ok := d.SafeSearchDomain("www.google.com")
@@ -866,7 +865,7 @@ func BenchmarkSafeSearch(b *testing.B) {
}
func BenchmarkSafeSearchParallel(b *testing.B) {
d, _ := newForTest(b, &Config{SafeSearchEnabled: true}, nil)
d := newForTest(b, &Config{SafeSearchEnabled: true}, nil)
b.Cleanup(d.Close)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {

View File

@@ -133,31 +133,34 @@ func matchDomainWildcard(host, wildcard string) (ok bool) {
// 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.
// Len implements the sort.Interface interface for legacyRewritesSorted.
func (a rewritesSorted) Len() (l int) { return len(a) }
// Swap implements the sort.Interface interface for rewritesSorted.
// Swap implements the sort.Interface interface for legacyRewritesSorted.
func (a rewritesSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
// Less implements the sort.Interface interface for rewritesSorted.
// Less implements the sort.Interface interface for legacyRewritesSorted.
func (a rewritesSorted) Less(i, j int) (less bool) {
ith, jth := a[i], a[j]
if ith.Type == dns.TypeCNAME && jth.Type != dns.TypeCNAME {
if a[i].Type == dns.TypeCNAME && a[j].Type != dns.TypeCNAME {
return true
} else if ith.Type != dns.TypeCNAME && jth.Type == dns.TypeCNAME {
} else if a[i].Type != dns.TypeCNAME && a[j].Type == dns.TypeCNAME {
return false
}
if iw, jw := isWildcard(ith.Domain), isWildcard(jth.Domain); iw != jw {
return jw
if isWildcard(a[i].Domain) {
if !isWildcard(a[j].Domain) {
return false
}
} else {
if isWildcard(a[j].Domain) {
return true
}
}
// Both are either wildcards or not.
return len(ith.Domain) > len(jth.Domain)
// Both are wildcards.
return len(a[i].Domain) > len(a[j].Domain)
}
// prepareRewrites normalizes and validates all legacy DNS rewrites.
@@ -310,3 +313,9 @@ func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request)
d.Config.ConfigModified()
}
func (d *DNSFilter) registerRewritesHandlers() {
d.Config.HTTPRegister(http.MethodGet, "/control/rewrite/list", d.handleRewriteList)
d.Config.HTTPRegister(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd)
d.Config.HTTPRegister(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete)
}

View File

@@ -12,7 +12,7 @@ import (
// TODO(e.burkov): All the tests in this file may and should me merged together.
func TestRewrites(t *testing.T) {
d, _ := newForTest(t, nil, nil)
d := newForTest(t, nil, nil)
t.Cleanup(d.Close)
d.Rewrites = []*LegacyRewrite{{
@@ -188,7 +188,7 @@ func TestRewrites(t *testing.T) {
}
func TestRewritesLevels(t *testing.T) {
d, _ := newForTest(t, nil, nil)
d := newForTest(t, nil, nil)
t.Cleanup(d.Close)
// Exact host, wildcard L2, wildcard L3.
d.Rewrites = []*LegacyRewrite{{
@@ -235,7 +235,7 @@ func TestRewritesLevels(t *testing.T) {
}
func TestRewritesExceptionCNAME(t *testing.T) {
d, _ := newForTest(t, nil, nil)
d := newForTest(t, nil, nil)
t.Cleanup(d.Close)
// Wildcard and exception for a sub-domain.
d.Rewrites = []*LegacyRewrite{{
@@ -286,7 +286,7 @@ func TestRewritesExceptionCNAME(t *testing.T) {
}
func TestRewritesExceptionIP(t *testing.T) {
d, _ := newForTest(t, nil, nil)
d := newForTest(t, nil, nil)
t.Cleanup(d.Close)
// Exception for AAAA record.
d.Rewrites = []*LegacyRewrite{{

View File

@@ -415,3 +415,17 @@ func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request)
aghhttp.Error(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
}
}
func (d *DNSFilter) registerSecurityHandlers() {
d.Config.HTTPRegister(http.MethodPost, "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
d.Config.HTTPRegister(http.MethodPost, "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
d.Config.HTTPRegister(http.MethodGet, "/control/safebrowsing/status", d.handleSafeBrowsingStatus)
d.Config.HTTPRegister(http.MethodPost, "/control/parental/enable", d.handleParentalEnable)
d.Config.HTTPRegister(http.MethodPost, "/control/parental/disable", d.handleParentalDisable)
d.Config.HTTPRegister(http.MethodGet, "/control/parental/status", d.handleParentalStatus)
d.Config.HTTPRegister(http.MethodPost, "/control/safesearch/enable", d.handleSafeSearchEnable)
d.Config.HTTPRegister(http.MethodPost, "/control/safesearch/disable", d.handleSafeSearchDisable)
d.Config.HTTPRegister(http.MethodGet, "/control/safesearch/status", d.handleSafeSearchStatus)
}

View File

@@ -107,7 +107,7 @@ func TestSafeBrowsingCache(t *testing.T) {
}
func TestSBPC_checkErrorUpstream(t *testing.T) {
d, _ := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
t.Cleanup(d.Close)
ups := aghtest.NewErrorUpstream()
@@ -128,7 +128,7 @@ func TestSBPC_checkErrorUpstream(t *testing.T) {
}
func TestSBPC(t *testing.T) {
d, _ := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
t.Cleanup(d.Close)
const hostname = "example.org"

View File

@@ -3,7 +3,7 @@ package home
import (
"bytes"
"fmt"
"net/netip"
"net"
"os"
"path/filepath"
"sync"
@@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
"github.com/AdguardTeam/AdGuardHome/internal/stats"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/dnsproxy/fastip"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
@@ -22,9 +23,10 @@ import (
yaml "gopkg.in/yaml.v3"
)
// dataDir is the name of a directory under the working one to store some
// persistent data.
const dataDir = "data"
const (
dataDir = "data" // data storage
filterDir = "filters" // cache location for downloaded filters, it's under DataDir
)
// logSettings are the logging settings part of the configuration file.
//
@@ -85,10 +87,10 @@ type configuration struct {
// It's reset after config is parsed
fileData []byte
BindHost netip.Addr `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to
BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server
BetaBindPort int `yaml:"beta_bind_port"` // BetaBindPort is the port for new client
Users []User `yaml:"users"` // Users that can access HTTP server
BindHost net.IP `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to
BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server
BetaBindPort int `yaml:"beta_bind_port"` // BetaBindPort is the port for new client
Users []User `yaml:"users"` // Users that can access HTTP server
// AuthAttempts is the maximum number of failed login attempts a user
// can do before being blocked.
AuthAttempts uint `yaml:"auth_attempts"`
@@ -106,16 +108,9 @@ type configuration struct {
DNS dnsConfig `yaml:"dns"`
TLS tlsConfigSettings `yaml:"tls"`
// Filters reflects the filters from [filtering.Config]. It's cloned to the
// config used in the filtering module at the startup. Afterwards it's
// cloned from the filtering module back here.
//
// TODO(e.burkov): Move all the filtering configuration fields into the
// only configuration subsection covering the changes with a single
// migration.
Filters []filtering.FilterYAML `yaml:"filters"`
WhitelistFilters []filtering.FilterYAML `yaml:"whitelist_filters"`
UserRules []string `yaml:"user_rules"`
Filters []filter `yaml:"filters"`
WhitelistFilters []filter `yaml:"whitelist_filters"`
UserRules []string `yaml:"user_rules"`
DHCP *dhcpd.ServerConfig `yaml:"dhcp"`
@@ -135,8 +130,8 @@ type configuration struct {
// field ordering is important -- yaml fields will mirror ordering from here
type dnsConfig struct {
BindHosts []netip.Addr `yaml:"bind_hosts"`
Port int `yaml:"port"`
BindHosts []net.IP `yaml:"bind_hosts"`
Port int `yaml:"port"`
// time interval for statistics (in days)
StatsInterval uint32 `yaml:"statistics_interval"`
@@ -150,7 +145,9 @@ type dnsConfig struct {
dnsforward.FilteringConfig `yaml:",inline"`
DnsfilterConf *filtering.Config `yaml:",inline"`
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours)
DnsfilterConf filtering.Config `yaml:",inline"`
// UpstreamTimeout is the timeout for querying upstream servers.
UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"`
@@ -196,20 +193,15 @@ type tlsConfigSettings struct {
//
// TODO(a.garipov, e.burkov): This global is awful and must be removed.
var config = &configuration{
BindPort: 3000,
BetaBindPort: 0,
BindHost: netip.IPv4Unspecified(),
AuthAttempts: 5,
AuthBlockMin: 15,
WebSessionTTLHours: 30 * 24,
BindPort: 3000,
BetaBindPort: 0,
BindHost: net.IP{0, 0, 0, 0},
AuthAttempts: 5,
AuthBlockMin: 15,
DNS: dnsConfig{
BindHosts: []netip.Addr{netip.IPv4Unspecified()},
Port: defaultPortDNS,
StatsInterval: 1,
QueryLogEnabled: true,
QueryLogFileEnabled: true,
QueryLogInterval: timeutil.Duration{Duration: 90 * timeutil.Day},
QueryLogMemSize: 1000,
BindHosts: []net.IP{{0, 0, 0, 0}},
Port: defaultPortDNS,
StatsInterval: 1,
FilteringConfig: dnsforward.FilteringConfig{
ProtectionEnabled: true, // whether or not use any of filtering features
BlockingMode: dnsforward.BlockingModeDefault,
@@ -230,42 +222,18 @@ var config = &configuration{
// was later increased to 300 due to https://github.com/AdguardTeam/AdGuardHome/issues/2257
MaxGoroutines: 300,
},
DnsfilterConf: &filtering.Config{
SafeBrowsingCacheSize: 1 * 1024 * 1024,
SafeSearchCacheSize: 1 * 1024 * 1024,
ParentalCacheSize: 1 * 1024 * 1024,
CacheTime: 30,
FilteringEnabled: true,
FiltersUpdateIntervalHours: 24,
},
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
UsePrivateRDNS: true,
FilteringEnabled: true, // whether or not use filter lists
FiltersUpdateIntervalHours: 24,
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
UsePrivateRDNS: true,
},
TLS: tlsConfigSettings{
PortHTTPS: defaultPortHTTPS,
PortDNSOverTLS: defaultPortTLS, // needs to be passed through to dnsproxy
PortDNSOverQUIC: defaultPortQUIC,
},
Filters: []filtering.FilterYAML{{
Filter: filtering.Filter{ID: 1},
Enabled: true,
URL: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt",
Name: "AdGuard DNS filter",
}, {
Filter: filtering.Filter{ID: 2},
Enabled: false,
URL: "https://adaway.org/hosts.txt",
Name: "AdAway Default Blocklist",
}},
DHCP: &dhcpd.ServerConfig{
LocalDomainName: "lan",
Conf4: dhcpd.V4ServerConf{
LeaseDuration: dhcpd.DefaultDHCPLeaseTTL,
ICMPTimeout: dhcpd.DefaultDHCPTimeoutICMP,
},
Conf6: dhcpd.V6ServerConf{
LeaseDuration: dhcpd.DefaultDHCPLeaseTTL,
},
},
Clients: &clientsConfig{
Sources: &clientSourcesConf{
@@ -287,6 +255,31 @@ var config = &configuration{
SchemaVersion: currentSchemaVersion,
}
// initConfig initializes default configuration for the current OS&ARCH
func initConfig() {
config.WebSessionTTLHours = 30 * 24
config.DNS.QueryLogEnabled = true
config.DNS.QueryLogFileEnabled = true
config.DNS.QueryLogInterval = timeutil.Duration{Duration: 90 * timeutil.Day}
config.DNS.QueryLogMemSize = 1000
config.DNS.CacheSize = 4 * 1024 * 1024
config.DNS.DnsfilterConf.SafeBrowsingCacheSize = 1 * 1024 * 1024
config.DNS.DnsfilterConf.SafeSearchCacheSize = 1 * 1024 * 1024
config.DNS.DnsfilterConf.ParentalCacheSize = 1 * 1024 * 1024
config.DNS.DnsfilterConf.CacheTime = 30
config.Filters = defaultFilters()
config.DHCP.Conf4.LeaseDuration = dhcpd.DefaultDHCPLeaseTTL
config.DHCP.Conf4.ICMPTimeout = dhcpd.DefaultDHCPTimeoutICMP
config.DHCP.Conf6.LeaseDuration = dhcpd.DefaultDHCPLeaseTTL
if ch := version.Channel(); ch == version.ChannelEdge || ch == version.ChannelDevelopment {
config.BetaBindPort = 3001
}
}
// getConfigFilename returns path to the current config file
func (c *configuration) getConfigFilename() string {
configFile, err := filepath.EvalSymlinks(Context.configFilename)
@@ -355,8 +348,8 @@ func parseConfig() (err error) {
return fmt.Errorf("validating udp ports: %w", err)
}
if !filtering.ValidateUpdateIvl(config.DNS.DnsfilterConf.FiltersUpdateIntervalHours) {
config.DNS.DnsfilterConf.FiltersUpdateIntervalHours = 24
if !checkFiltersUpdateIntervalHours(config.DNS.FiltersUpdateIntervalHours) {
config.DNS.FiltersUpdateIntervalHours = 24
}
if config.DNS.UpstreamTimeout.Duration == 0 {
@@ -425,11 +418,10 @@ func (c *configuration) write() (err error) {
config.DNS.AnonymizeClientIP = dc.AnonymizeClientIP
}
if Context.filters != nil {
Context.filters.WriteDiskConfig(config.DNS.DnsfilterConf)
config.Filters = config.DNS.DnsfilterConf.Filters
config.WhitelistFilters = config.DNS.DnsfilterConf.WhitelistFilters
config.UserRules = config.DNS.DnsfilterConf.UserRules
if Context.dnsFilter != nil {
c := filtering.Config{}
Context.dnsFilter.WriteDiskConfig(&c)
config.DNS.DnsfilterConf = c
}
if s := Context.dnsServer; s != nil {

View File

@@ -3,8 +3,8 @@ package home
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/netip"
"net/url"
"runtime"
"strings"
@@ -20,11 +20,11 @@ import (
// appendDNSAddrs is a convenient helper for appending a formatted form of DNS
// addresses to a slice of strings.
func appendDNSAddrs(dst []string, addrs ...netip.Addr) (res []string) {
func appendDNSAddrs(dst []string, addrs ...net.IP) (res []string) {
for _, addr := range addrs {
var hostport string
if config.DNS.Port != defaultPortDNS {
hostport = netip.AddrPortFrom(addr, uint16(config.DNS.Port)).String()
hostport = netutil.JoinHostPort(addr.String(), config.DNS.Port)
} else {
hostport = addr.String()
}
@@ -38,7 +38,7 @@ func appendDNSAddrs(dst []string, addrs ...netip.Addr) (res []string) {
// appendDNSAddrsWithIfaces formats and appends all DNS addresses from src to
// dst. It also adds the IP addresses of all network interfaces if src contains
// an unspecified IP address.
func appendDNSAddrsWithIfaces(dst []string, src []netip.Addr) (res []string, err error) {
func appendDNSAddrsWithIfaces(dst []string, src []net.IP) (res []string, err error) {
ifacesAdded := false
for _, h := range src {
if !h.IsUnspecified() {
@@ -71,9 +71,7 @@ func appendDNSAddrsWithIfaces(dst []string, src []netip.Addr) (res []string, err
// on, including the addresses on all interfaces in cases of unspecified IPs.
func collectDNSAddresses() (addrs []string, err error) {
if hosts := config.DNS.BindHosts; len(hosts) == 0 {
addr := aghnet.IPv4Localhost()
addrs = appendDNSAddrs(addrs, addr)
addrs = appendDNSAddrs(addrs, net.IP{127, 0, 0, 1})
} else {
addrs, err = appendDNSAddrsWithIfaces(addrs, hosts)
if err != nil {
@@ -293,7 +291,7 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) {
}
httpsURL := &url.URL{
Scheme: aghhttp.SchemeHTTPS,
Scheme: schemeHTTPS,
Host: hostPort,
Path: r.URL.Path,
RawQuery: r.URL.RawQuery,
@@ -309,7 +307,7 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) {
//
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin.
originURL := &url.URL{
Scheme: aghhttp.SchemeHTTP,
Scheme: schemeHTTP,
Host: r.Host,
}
w.Header().Set("Access-Control-Allow-Origin", originURL.String())

View File

@@ -1,4 +1,4 @@
package filtering
package home
import (
"encoding/json"
@@ -34,7 +34,7 @@ func validateFilterURL(urlStr string) (err error) {
return fmt.Errorf("checking filter url: %w", err)
}
if s := url.Scheme; s != aghhttp.SchemeHTTP && s != aghhttp.SchemeHTTPS {
if s := url.Scheme; s != schemeHTTP && s != schemeHTTPS {
return fmt.Errorf("checking filter url: invalid scheme %q", s)
}
@@ -47,7 +47,7 @@ type filterAddJSON struct {
Whitelist bool `json:"whitelist"`
}
func (d *DNSFilter) handleFilteringAddURL(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringAddURL(w http.ResponseWriter, r *http.Request) {
fj := filterAddJSON{}
err := json.NewDecoder(r.Body).Decode(&fj)
if err != nil {
@@ -65,14 +65,14 @@ func (d *DNSFilter) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
}
// Check for duplicates
if d.filterExists(fj.URL) {
if filterExists(fj.URL) {
aghhttp.Error(r, w, http.StatusBadRequest, "Filter URL already added -- %s", fj.URL)
return
}
// Set necessary properties
filt := FilterYAML{
filt := filter{
Enabled: true,
URL: fj.URL,
Name: fj.Name,
@@ -81,7 +81,7 @@ func (d *DNSFilter) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
filt.ID = assignUniqueFilterID()
// Download the filter contents
ok, err := d.update(&filt)
ok, err := f.update(&filt)
if err != nil {
aghhttp.Error(
r,
@@ -109,14 +109,14 @@ func (d *DNSFilter) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
// URL is assumed valid so append it to filters, update config, write new
// file and reload it to engines.
if !d.filterAdd(filt) {
if !filterAdd(filt) {
aghhttp.Error(r, w, http.StatusBadRequest, "Filter URL already added -- %s", filt.URL)
return
}
d.ConfigModified()
d.EnableFilters(true)
onConfigModified()
enableFilters(true)
_, err = fmt.Fprintf(w, "OK %d rules\n", filt.RulesCount)
if err != nil {
@@ -124,7 +124,7 @@ func (d *DNSFilter) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
}
}
func (d *DNSFilter) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) {
type request struct {
URL string `json:"url"`
Whitelist bool `json:"whitelist"`
@@ -138,23 +138,23 @@ func (d *DNSFilter) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ
return
}
d.filtersMu.Lock()
filters := &d.Filters
config.Lock()
filters := &config.Filters
if req.Whitelist {
filters = &d.WhitelistFilters
filters = &config.WhitelistFilters
}
var deleted FilterYAML
var newFilters []FilterYAML
for _, flt := range *filters {
if flt.URL != req.URL {
newFilters = append(newFilters, flt)
var deleted filter
var newFilters []filter
for _, f := range *filters {
if f.URL != req.URL {
newFilters = append(newFilters, f)
continue
}
deleted = flt
path := flt.Path(d.DataDir)
deleted = f
path := f.Path()
err = os.Rename(path, path+".old")
if err != nil {
log.Error("deleting filter %q: %s", path, err)
@@ -162,10 +162,10 @@ func (d *DNSFilter) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ
}
*filters = newFilters
d.filtersMu.Unlock()
config.Unlock()
d.ConfigModified()
d.EnableFilters(true)
onConfigModified()
enableFilters(true)
// NOTE: The old files "filter.txt.old" aren't deleted. It's not really
// necessary, but will require the additional complicated code to run
@@ -191,51 +191,55 @@ type filterURLReq struct {
Whitelist bool `json:"whitelist"`
}
func (d *DNSFilter) handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
fj := filterURLReq{}
err := json.NewDecoder(r.Body).Decode(&fj)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "decoding request: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
return
}
if fj.Data == nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", errors.Error("data is absent"))
err = errors.Error("data cannot be null")
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
err = validateFilterURL(fj.Data.URL)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "invalid url: %s", err)
err = fmt.Errorf("invalid url: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
filt := FilterYAML{
filt := filter{
Enabled: fj.Data.Enabled,
Name: fj.Data.Name,
URL: fj.Data.URL,
}
status := d.filterSetProperties(fj.URL, filt, fj.Whitelist)
status := f.filterSetProperties(fj.URL, filt, fj.Whitelist)
if (status & statusFound) == 0 {
aghhttp.Error(r, w, http.StatusBadRequest, "URL doesn't exist")
http.Error(w, "URL doesn't exist", http.StatusBadRequest)
return
}
if (status & statusURLExists) != 0 {
aghhttp.Error(r, w, http.StatusBadRequest, "URL already exists")
http.Error(w, "URL already exists", http.StatusBadRequest)
return
}
d.ConfigModified()
onConfigModified()
restart := (status & statusEnabledChanged) != 0
if (status&statusUpdateRequired) != 0 && fj.Data.Enabled {
// download new filter and apply its rules.
nUpdated := d.refreshFilters(!fj.Whitelist, fj.Whitelist, false)
// download new filter and apply its rules
flags := filterRefreshBlocklists
if fj.Whitelist {
flags = filterRefreshAllowlists
}
nUpdated, _ := f.refreshFilters(flags, true)
// if at least 1 filter has been updated, refreshFilters() restarts the filtering automatically
// if not - we restart the filtering ourselves
restart = false
@@ -245,11 +249,11 @@ func (d *DNSFilter) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
}
if restart {
d.EnableFilters(true)
enableFilters(true)
}
}
func (d *DNSFilter) handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
// This use of ReadAll is safe, because request's body is now limited.
body, err := io.ReadAll(r.Body)
if err != nil {
@@ -258,12 +262,12 @@ func (d *DNSFilter) handleFilteringSetRules(w http.ResponseWriter, r *http.Reque
return
}
d.UserRules = strings.Split(string(body), "\n")
d.ConfigModified()
d.EnableFilters(true)
config.UserRules = strings.Split(string(body), "\n")
onConfigModified()
enableFilters(true)
}
func (d *DNSFilter) handleFilteringRefresh(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Request) {
type Req struct {
White bool `json:"whitelist"`
}
@@ -281,27 +285,35 @@ func (d *DNSFilter) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
return
}
var ok bool
resp.Updated, _, ok = d.tryRefreshFilters(!req.White, req.White, true)
if !ok {
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"filters update procedure is already running",
)
flags := filterRefreshBlocklists
if req.White {
flags = filterRefreshAllowlists
}
func() {
// Temporarily unlock the Context.controlLock because the
// f.refreshFilters waits for it to be unlocked but it's
// actually locked in ensure wrapper.
//
// TODO(e.burkov): Reconsider this messy syncing process.
Context.controlLock.Unlock()
defer Context.controlLock.Lock()
resp.Updated, err = f.refreshFilters(flags|filterRefreshForce, false)
}()
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(resp)
js, err := json.Marshal(resp)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(js)
}
type filterJSON struct {
@@ -321,7 +333,7 @@ type filteringConfig struct {
Enabled bool `json:"enabled"`
}
func filterToJSON(f FilterYAML) filterJSON {
func filterToJSON(f filter) filterJSON {
fj := filterJSON{
ID: f.ID,
Enabled: f.Enabled,
@@ -338,21 +350,21 @@ func filterToJSON(f FilterYAML) filterJSON {
}
// Get filtering configuration
func (d *DNSFilter) handleFilteringStatus(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringStatus(w http.ResponseWriter, r *http.Request) {
resp := filteringConfig{}
d.filtersMu.RLock()
resp.Enabled = d.FilteringEnabled
resp.Interval = d.FiltersUpdateIntervalHours
for _, f := range d.Filters {
config.RLock()
resp.Enabled = config.DNS.FilteringEnabled
resp.Interval = config.DNS.FiltersUpdateIntervalHours
for _, f := range config.Filters {
fj := filterToJSON(f)
resp.Filters = append(resp.Filters, fj)
}
for _, f := range d.WhitelistFilters {
for _, f := range config.WhitelistFilters {
fj := filterToJSON(f)
resp.WhitelistFilters = append(resp.WhitelistFilters, fj)
}
resp.UserRules = d.UserRules
d.filtersMu.RUnlock()
resp.UserRules = config.UserRules
config.RUnlock()
jsonVal, err := json.Marshal(resp)
if err != nil {
@@ -368,7 +380,7 @@ func (d *DNSFilter) handleFilteringStatus(w http.ResponseWriter, r *http.Request
}
// Set filtering configuration
func (d *DNSFilter) handleFilteringConfig(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request) {
req := filteringConfig{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
@@ -377,22 +389,22 @@ func (d *DNSFilter) handleFilteringConfig(w http.ResponseWriter, r *http.Request
return
}
if !ValidateUpdateIvl(req.Interval) {
if !checkFiltersUpdateIntervalHours(req.Interval) {
aghhttp.Error(r, w, http.StatusBadRequest, "Unsupported interval")
return
}
func() {
d.filtersMu.Lock()
defer d.filtersMu.Unlock()
config.Lock()
defer config.Unlock()
d.FilteringEnabled = req.Enabled
d.FiltersUpdateIntervalHours = req.Interval
config.DNS.FilteringEnabled = req.Enabled
config.DNS.FiltersUpdateIntervalHours = req.Interval
}()
d.ConfigModified()
d.EnableFilters(true)
onConfigModified()
enableFilters(true)
}
type checkHostRespRule struct {
@@ -423,15 +435,15 @@ type checkHostResp struct {
FilterID int64 `json:"filter_id"`
}
func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
host := r.URL.Query().Get("name")
func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
host := q.Get("name")
setts := d.GetConfig()
setts := Context.dnsFilter.GetConfig()
setts.FilteringEnabled = true
setts.ProtectionEnabled = true
d.ApplyBlockedServices(&setts, nil)
result, err := d.CheckHost(host, dns.TypeA, &setts)
Context.dnsFilter.ApplyBlockedServices(&setts, nil, true)
result, err := Context.dnsFilter.CheckHost(host, dns.TypeA, &setts)
if err != nil {
aghhttp.Error(
r,
@@ -445,20 +457,18 @@ func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
return
}
rulesLen := len(result.Rules)
resp := checkHostResp{
Reason: result.Reason.String(),
SvcName: result.ServiceName,
CanonName: result.CanonName,
IPList: result.IPList,
Rules: make([]*checkHostRespRule, len(result.Rules)),
}
resp := checkHostResp{}
resp.Reason = result.Reason.String()
resp.SvcName = result.ServiceName
resp.CanonName = result.CanonName
resp.IPList = result.IPList
if rulesLen > 0 {
if len(result.Rules) > 0 {
resp.FilterID = result.Rules[0].FilterListID
resp.Rule = result.Rules[0].Text
}
resp.Rules = make([]*checkHostRespRule, len(result.Rules))
for i, r := range result.Rules {
resp.Rules[i] = &checkHostRespRule{
FilterListID: r.FilterListID,
@@ -466,51 +476,28 @@ func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
}
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(resp)
js, err := json.Marshal(resp)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "encoding response: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(js)
}
// RegisterFilteringHandlers - register handlers
func (d *DNSFilter) RegisterFilteringHandlers() {
registerHTTP := d.HTTPRegister
if registerHTTP == nil {
return
}
registerHTTP(http.MethodPost, "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
registerHTTP(http.MethodPost, "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
registerHTTP(http.MethodGet, "/control/safebrowsing/status", d.handleSafeBrowsingStatus)
registerHTTP(http.MethodPost, "/control/parental/enable", d.handleParentalEnable)
registerHTTP(http.MethodPost, "/control/parental/disable", d.handleParentalDisable)
registerHTTP(http.MethodGet, "/control/parental/status", d.handleParentalStatus)
registerHTTP(http.MethodPost, "/control/safesearch/enable", d.handleSafeSearchEnable)
registerHTTP(http.MethodPost, "/control/safesearch/disable", d.handleSafeSearchDisable)
registerHTTP(http.MethodGet, "/control/safesearch/status", d.handleSafeSearchStatus)
registerHTTP(http.MethodGet, "/control/rewrite/list", d.handleRewriteList)
registerHTTP(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd)
registerHTTP(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete)
registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesAvailableServices)
registerHTTP(http.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList)
registerHTTP(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet)
registerHTTP(http.MethodGet, "/control/filtering/status", d.handleFilteringStatus)
registerHTTP(http.MethodPost, "/control/filtering/config", d.handleFilteringConfig)
registerHTTP(http.MethodPost, "/control/filtering/add_url", d.handleFilteringAddURL)
registerHTTP(http.MethodPost, "/control/filtering/remove_url", d.handleFilteringRemoveURL)
registerHTTP(http.MethodPost, "/control/filtering/set_url", d.handleFilteringSetURL)
registerHTTP(http.MethodPost, "/control/filtering/refresh", d.handleFilteringRefresh)
registerHTTP(http.MethodPost, "/control/filtering/set_rules", d.handleFilteringSetRules)
registerHTTP(http.MethodGet, "/control/filtering/check_host", d.handleCheckHost)
func (f *Filtering) RegisterFilteringHandlers() {
httpRegister(http.MethodGet, "/control/filtering/status", f.handleFilteringStatus)
httpRegister(http.MethodPost, "/control/filtering/config", f.handleFilteringConfig)
httpRegister(http.MethodPost, "/control/filtering/add_url", f.handleFilteringAddURL)
httpRegister(http.MethodPost, "/control/filtering/remove_url", f.handleFilteringRemoveURL)
httpRegister(http.MethodPost, "/control/filtering/set_url", f.handleFilteringSetURL)
httpRegister(http.MethodPost, "/control/filtering/refresh", f.handleFilteringRefresh)
httpRegister(http.MethodPost, "/control/filtering/set_rules", f.handleFilteringSetRules)
httpRegister(http.MethodGet, "/control/filtering/check_host", f.handleCheckHost)
}
// ValidateUpdateIvl returns false if i is not a valid filters update interval.
func ValidateUpdateIvl(i uint32) bool {
func checkFiltersUpdateIntervalHours(i uint32) bool {
return i == 0 || i == 1 || i == 12 || i == 1*24 || i == 3*24 || i == 7*24
}

View File

@@ -5,8 +5,8 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/netip"
"os"
"os/exec"
"path/filepath"
@@ -75,9 +75,9 @@ func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request
}
type checkConfReqEnt struct {
IP netip.Addr `json:"ip"`
Port int `json:"port"`
Autofix bool `json:"autofix"`
IP net.IP `json:"ip"`
Port int `json:"port"`
Autofix bool `json:"autofix"`
}
type checkConfReq struct {
@@ -128,7 +128,7 @@ func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err
// unbound after install.
}
return aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(portInt)))
return aghnet.CheckPort("tcp", req.Web.IP, portInt)
}
// validateDNS returns error if the DNS part of the initial configuration can't
@@ -153,13 +153,13 @@ func (req *checkConfReq) validateDNS(
return false, err
}
err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.DNS.IP, uint16(port)))
err = aghnet.CheckPort("tcp", req.DNS.IP, port)
if err != nil {
return false, err
}
}
err = aghnet.CheckPort("udp", netip.AddrPortFrom(req.DNS.IP, uint16(port)))
err = aghnet.CheckPort("udp", req.DNS.IP, port)
if !aghnet.IsAddrInUse(err) {
return false, err
}
@@ -171,7 +171,7 @@ func (req *checkConfReq) validateDNS(
log.Error("disabling DNSStubListener: %s", err)
}
err = aghnet.CheckPort("udp", netip.AddrPortFrom(req.DNS.IP, uint16(port)))
err = aghnet.CheckPort("udp", req.DNS.IP, port)
canAutofix = false
}
@@ -213,7 +213,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request)
// handleStaticIP - handles static IP request
// It either checks if we have a static IP
// Or if set=true, it tries to set it
func handleStaticIP(ip netip.Addr, set bool) staticIPJSON {
func handleStaticIP(ip net.IP, set bool) staticIPJSON {
resp := staticIPJSON{}
interfaceName := aghnet.InterfaceByIP(ip)
@@ -321,8 +321,8 @@ func disableDNSStubListener() error {
}
type applyConfigReqEnt struct {
IP netip.Addr `json:"ip"`
Port int `json:"port"`
IP net.IP `json:"ip"`
Port int `json:"port"`
}
type applyConfigReq struct {
@@ -388,14 +388,14 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
return
}
err = aghnet.CheckPort("udp", netip.AddrPortFrom(req.DNS.IP, uint16(req.DNS.Port)))
err = aghnet.CheckPort("udp", req.DNS.IP, req.DNS.Port)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.DNS.IP, uint16(req.DNS.Port)))
err = aghnet.CheckPort("tcp", req.DNS.IP, req.DNS.Port)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
@@ -408,7 +408,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
Context.firstRun = false
config.BindHost = req.Web.IP
config.BindPort = req.Web.Port
config.DNS.BindHosts = []netip.Addr{req.DNS.IP}
config.DNS.BindHosts = []net.IP{req.DNS.IP}
config.DNS.Port = req.DNS.Port
// TODO(e.burkov): StartMods() should be put in a separate goroutine at the
@@ -481,9 +481,9 @@ func decodeApplyConfigReq(r io.Reader) (req *applyConfigReq, restartHTTP bool, e
return nil, false, errors.Error("ports cannot be 0")
}
restartHTTP = config.BindHost != req.Web.IP || config.BindPort != req.Web.Port
restartHTTP = !config.BindHost.Equal(req.Web.IP) || config.BindPort != req.Web.Port
if restartHTTP {
err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(req.Web.Port)))
err = aghnet.CheckPort("tcp", req.Web.IP, req.Web.Port)
if err != nil {
return nil, false, fmt.Errorf(
"checking address %s:%d: %w",
@@ -509,9 +509,9 @@ func (web *Web) registerInstallHandlers() {
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default checkConfigReqEnt.
type checkConfigReqEntBeta struct {
IP []netip.Addr `json:"ip"`
Port int `json:"port"`
Autofix bool `json:"autofix"`
IP []net.IP `json:"ip"`
Port int `json:"port"`
Autofix bool `json:"autofix"`
}
// checkConfigReqBeta is a struct representing new client's config check request
@@ -586,8 +586,8 @@ func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Requ
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default applyConfigReqEnt.
type applyConfigReqEntBeta struct {
IP []netip.Addr `json:"ip"`
Port int `json:"port"`
IP []net.IP `json:"ip"`
Port int `json:"port"`
}
// applyConfigReqBeta is a struct representing new client's config setting

View File

@@ -3,7 +3,6 @@ package home
import (
"fmt"
"net"
"net/netip"
"net/url"
"os"
"path/filepath"
@@ -32,10 +31,7 @@ const (
// Called by other modules when configuration is changed
func onConfigModified() {
err := config.write()
if err != nil {
log.Error("writing config: %s", err)
}
_ = config.write()
}
// initDNSServer creates an instance of the dnsforward.Server
@@ -75,11 +71,11 @@ func initDNSServer() (err error) {
}
Context.queryLog = querylog.New(conf)
Context.filters, err = filtering.New(config.DNS.DnsfilterConf, nil)
if err != nil {
// Don't wrap the error, since it's informative enough as is.
return err
}
filterConf := config.DNS.DnsfilterConf
filterConf.EtcHosts = Context.etcHosts
filterConf.ConfigModified = onConfigModified
filterConf.HTTPRegister = httpRegister
Context.dnsFilter = filtering.New(&filterConf, nil)
var privateNets netutil.SubnetSet
switch len(config.DNS.PrivateNets) {
@@ -87,10 +83,13 @@ func initDNSServer() (err error) {
// Use an optimized locally-served matcher.
privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
case 1:
privateNets, err = netutil.ParseSubnet(config.DNS.PrivateNets[0])
var n *net.IPNet
n, err = netutil.ParseSubnet(config.DNS.PrivateNets[0])
if err != nil {
return fmt.Errorf("preparing the set of private subnets: %w", err)
}
privateNets = n
default:
var nets []*net.IPNet
nets, err = netutil.ParseSubnets(config.DNS.PrivateNets...)
@@ -102,13 +101,15 @@ func initDNSServer() (err error) {
}
p := dnsforward.DNSCreateParams{
DNSFilter: Context.filters,
DNSFilter: Context.dnsFilter,
Stats: Context.stats,
QueryLog: Context.queryLog,
PrivateNets: privateNets,
Anonymizer: anonymizer,
LocalDomain: config.DHCP.LocalDomainName,
DHCPServer: Context.dhcpServer,
}
if Context.dhcpServer != nil {
p.DHCPServer = Context.dhcpServer
}
Context.dnsServer, err = dnsforward.NewServer(p)
@@ -142,6 +143,7 @@ func initDNSServer() (err error) {
Context.whois = initWHOIS(&Context.clients)
}
Context.filters.Init()
return nil
}
@@ -165,27 +167,33 @@ func onDNSRequest(pctx *proxy.DNSContext) {
}
}
func ipsToTCPAddrs(ips []netip.Addr, port int) (tcpAddrs []*net.TCPAddr) {
func ipsToTCPAddrs(ips []net.IP, port int) (tcpAddrs []*net.TCPAddr) {
if ips == nil {
return nil
}
tcpAddrs = make([]*net.TCPAddr, 0, len(ips))
for _, ip := range ips {
tcpAddrs = append(tcpAddrs, net.TCPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16(port))))
tcpAddrs = make([]*net.TCPAddr, len(ips))
for i, ip := range ips {
tcpAddrs[i] = &net.TCPAddr{
IP: ip,
Port: port,
}
}
return tcpAddrs
}
func ipsToUDPAddrs(ips []netip.Addr, port int) (udpAddrs []*net.UDPAddr) {
func ipsToUDPAddrs(ips []net.IP, port int) (udpAddrs []*net.UDPAddr) {
if ips == nil {
return nil
}
udpAddrs = make([]*net.UDPAddr, 0, len(ips))
for _, ip := range ips {
udpAddrs = append(udpAddrs, net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16(port))))
udpAddrs = make([]*net.UDPAddr, len(ips))
for i, ip := range ips {
udpAddrs[i] = &net.UDPAddr{
IP: ip,
Port: port,
}
}
return udpAddrs
@@ -195,7 +203,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
dnsConf := config.DNS
hosts := dnsConf.BindHosts
if len(hosts) == 0 {
hosts = []netip.Addr{aghnet.IPv4Localhost()}
hosts = []net.IP{{127, 0, 0, 1}}
}
newConf = dnsforward.ServerConfig{
@@ -249,7 +257,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
return newConf, nil
}
func newDNSCrypt(hosts []netip.Addr, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) {
func newDNSCrypt(hosts []net.IP, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) {
if tlsConf.DNSCryptConfigFile == "" {
return dnscc, errors.Error("no dnscrypt_config_file")
}
@@ -327,12 +335,9 @@ func getDNSEncryption() (de dnsEncryption) {
// applyAdditionalFiltering adds additional client information and settings if
// the client has them.
func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering.Settings) {
// pref is a prefix for logging messages around the scope.
const pref = "applying filters"
Context.dnsFilter.ApplyBlockedServices(setts, nil, true)
Context.filters.ApplyBlockedServices(setts, nil)
log.Debug("%s: looking for client with ip %s and clientid %q", pref, clientIP, clientID)
log.Debug("looking up settings for client with ip %s and clientid %q", clientIP, clientID)
if clientIP == nil {
return
@@ -344,16 +349,16 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering
if !ok {
c, ok = Context.clients.Find(clientIP.String())
if !ok {
log.Debug("%s: no clients with ip %s and clientid %q", pref, clientIP, clientID)
log.Debug("client with ip %s and clientid %q not found", clientIP, clientID)
return
}
}
log.Debug("%s: using settings for client %q (%s; %q)", pref, c.Name, clientIP, clientID)
log.Debug("using settings for client %q with ip %s and clientid %q", c.Name, clientIP, clientID)
if c.UseOwnBlockedServices {
Context.filters.ApplyBlockedServices(setts, c.BlockedServices)
Context.dnsFilter.ApplyBlockedServices(setts, c.BlockedServices, false)
}
setts.ClientName = c.Name
@@ -376,7 +381,7 @@ func startDNSServer() error {
return fmt.Errorf("unable to start forwarding DNS server: Already running")
}
Context.filters.EnableFilters(false)
enableFiltersLocked(false)
Context.clients.Start()
@@ -385,6 +390,7 @@ func startDNSServer() error {
return fmt.Errorf("couldn't start forwarding DNS server: %w", err)
}
Context.dnsFilter.Start()
Context.filters.Start()
Context.stats.Start()
Context.queryLog.Start()
@@ -443,7 +449,10 @@ func closeDNSServer() {
Context.dnsServer = nil
}
Context.filters.Close()
if Context.dnsFilter != nil {
Context.dnsFilter.Close()
Context.dnsFilter = nil
}
if Context.stats != nil {
err := Context.stats.Close()
@@ -460,5 +469,7 @@ func closeDNSServer() {
Context.queryLog = nil
}
log.Debug("all dns modules are closed")
Context.filters.Close()
log.Debug("Closed all DNS modules")
}

View File

@@ -1,4 +1,4 @@
package filtering
package home
import (
"bufio"
@@ -8,29 +8,63 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/slices"
)
// filterDir is the subdirectory of a data directory to store downloaded
// filters.
const filterDir = "filters"
var nextFilterID = time.Now().Unix() // semi-stable way to generate an unique ID
// nextFilterID is a way to seed a unique ID generation.
//
// TODO(e.burkov): Use more deterministic approach.
var nextFilterID = time.Now().Unix()
// Filtering - module object
type Filtering struct {
// conf FilteringConf
refreshStatus uint32 // 0:none; 1:in progress
refreshLock sync.Mutex
filterTitleRegexp *regexp.Regexp
}
// FilterYAML respresents a filter list in the configuration file.
//
// TODO(e.burkov): Investigate if the field oredering is important.
type FilterYAML struct {
// Init - initialize the module
func (f *Filtering) Init() {
f.filterTitleRegexp = regexp.MustCompile(`^! Title: +(.*)$`)
_ = os.MkdirAll(filepath.Join(Context.getDataDir(), filterDir), 0o755)
f.loadFilters(config.Filters)
f.loadFilters(config.WhitelistFilters)
deduplicateFilters()
updateUniqueFilterID(config.Filters)
updateUniqueFilterID(config.WhitelistFilters)
}
// Start - start the module
func (f *Filtering) Start() {
f.RegisterFilteringHandlers()
// Here we should start updating filters,
// but currently we can't wake up the periodic task to do so.
// So for now we just start this periodic task from here.
go f.periodicallyRefreshFilters()
}
// Close - close the module
func (f *Filtering) Close() {
}
func defaultFilters() []filter {
return []filter{
{Filter: filtering.Filter{ID: 1}, Enabled: true, URL: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt", Name: "AdGuard DNS filter"},
{Filter: filtering.Filter{ID: 2}, Enabled: false, URL: "https://adaway.org/hosts.txt", Name: "AdAway Default Blocklist"},
}
}
// field ordering is important -- yaml fields will mirror ordering from here
type filter struct {
Enabled bool
URL string // URL or a file path
Name string `yaml:"name"`
@@ -39,108 +73,91 @@ type FilterYAML struct {
checksum uint32 // checksum of the file data
white bool
Filter `yaml:",inline"`
}
// Clear filter rules
func (filter *FilterYAML) unload() {
filter.RulesCount = 0
filter.checksum = 0
}
// Path to the filter contents
func (filter *FilterYAML) Path(dataDir string) string {
return filepath.Join(dataDir, filterDir, strconv.FormatInt(filter.ID, 10)+".txt")
filtering.Filter `yaml:",inline"`
}
const (
statusFound = 1 << iota
statusEnabledChanged
statusURLChanged
statusURLExists
statusUpdateRequired
statusFound = 1
statusEnabledChanged = 2
statusURLChanged = 4
statusURLExists = 8
statusUpdateRequired = 0x10
)
// Update properties for a filter specified by its URL
// Return status* flags.
func (d *DNSFilter) filterSetProperties(url string, newf FilterYAML, whitelist bool) int {
func (f *Filtering) filterSetProperties(url string, newf filter, whitelist bool) int {
r := 0
d.filtersMu.Lock()
defer d.filtersMu.Unlock()
config.Lock()
defer config.Unlock()
filters := d.Filters
filters := &config.Filters
if whitelist {
filters = d.WhitelistFilters
filters = &config.WhitelistFilters
}
i := slices.IndexFunc(filters, func(filt FilterYAML) bool {
return filt.URL == url
})
if i == -1 {
return 0
}
filt := &filters[i]
log.Debug("filter: set properties: %s: {%s %s %v}", filt.URL, newf.Name, newf.URL, newf.Enabled)
filt.Name = newf.Name
if filt.URL != newf.URL {
r |= statusURLChanged | statusUpdateRequired
if d.filterExistsNoLock(newf.URL) {
return statusURLExists
for i := range *filters {
filt := &(*filters)[i]
if filt.URL != url {
continue
}
filt.URL = newf.URL
filt.unload()
filt.LastUpdated = time.Time{}
filt.checksum = 0
filt.RulesCount = 0
}
log.Debug("filter: set properties: %s: {%s %s %v}",
filt.URL, newf.Name, newf.URL, newf.Enabled)
filt.Name = newf.Name
if filt.Enabled != newf.Enabled {
r |= statusEnabledChanged
filt.Enabled = newf.Enabled
if filt.Enabled {
if (r & statusURLChanged) == 0 {
err := d.load(filt)
if err != nil {
// TODO(e.burkov): It seems the error is only returned when
// the file exists and couldn't be open. Investigate and
// improve.
log.Error("loading filter %d: %s", filt.ID, err)
filt.LastUpdated = time.Time{}
filt.checksum = 0
filt.RulesCount = 0
r |= statusUpdateRequired
}
if filt.URL != newf.URL {
r |= statusURLChanged | statusUpdateRequired
if filterExistsNoLock(newf.URL) {
return statusURLExists
}
} else {
filt.URL = newf.URL
filt.unload()
filt.LastUpdated = time.Time{}
filt.checksum = 0
filt.RulesCount = 0
}
}
return r | statusFound
if filt.Enabled != newf.Enabled {
r |= statusEnabledChanged
filt.Enabled = newf.Enabled
if filt.Enabled {
if (r & statusURLChanged) == 0 {
e := f.load(filt)
if e != nil {
// This isn't a fatal error,
// because it may occur when someone removes the file from disk.
filt.LastUpdated = time.Time{}
filt.checksum = 0
filt.RulesCount = 0
r |= statusUpdateRequired
}
}
} else {
filt.unload()
}
}
return r | statusFound
}
return 0
}
// Return TRUE if a filter with this URL exists
func (d *DNSFilter) filterExists(url string) bool {
d.filtersMu.RLock()
defer d.filtersMu.RUnlock()
r := d.filterExistsNoLock(url)
func filterExists(url string) bool {
config.RLock()
r := filterExistsNoLock(url)
config.RUnlock()
return r
}
func (d *DNSFilter) filterExistsNoLock(url string) bool {
for _, f := range d.Filters {
func filterExistsNoLock(url string) bool {
for _, f := range config.Filters {
if f.URL == url {
return true
}
}
for _, f := range d.WhitelistFilters {
for _, f := range config.WhitelistFilters {
if f.URL == url {
return true
}
@@ -150,26 +167,26 @@ func (d *DNSFilter) filterExistsNoLock(url string) bool {
// Add a filter
// Return FALSE if a filter with this URL exists
func (d *DNSFilter) filterAdd(flt FilterYAML) bool {
d.filtersMu.Lock()
defer d.filtersMu.Unlock()
func filterAdd(f filter) bool {
config.Lock()
defer config.Unlock()
// Check for duplicates
if d.filterExistsNoLock(flt.URL) {
if filterExistsNoLock(f.URL) {
return false
}
if flt.white {
d.WhitelistFilters = append(d.WhitelistFilters, flt)
if f.white {
config.WhitelistFilters = append(config.WhitelistFilters, f)
} else {
d.Filters = append(d.Filters, flt)
config.Filters = append(config.Filters, f)
}
return true
}
// Load filters from the disk
// And if any filter has zero ID, assign a new one
func (d *DNSFilter) loadFilters(array []FilterYAML) {
func (f *Filtering) loadFilters(array []filter) {
for i := range array {
filter := &array[i] // otherwise we're operating on a copy
if filter.ID == 0 {
@@ -181,30 +198,32 @@ func (d *DNSFilter) loadFilters(array []FilterYAML) {
continue
}
err := d.load(filter)
err := f.load(filter)
if err != nil {
log.Error("Couldn't load filter %d contents due to %s", filter.ID, err)
}
}
}
func deduplicateFilters(filters []FilterYAML) (deduplicated []FilterYAML) {
urls := stringutil.NewSet()
lastIdx := 0
for _, filter := range filters {
if !urls.Has(filter.URL) {
urls.Add(filter.URL)
filters[lastIdx] = filter
lastIdx++
func deduplicateFilters() {
// Deduplicate filters
i := 0 // output index, used for deletion later
urls := map[string]bool{}
for _, filter := range config.Filters {
if _, ok := urls[filter.URL]; !ok {
// we didn't see it before, keep it
urls[filter.URL] = true // remember the URL
config.Filters[i] = filter
i++
}
}
return filters[:lastIdx]
// all entries we want to keep are at front, delete the rest
config.Filters = config.Filters[:i]
}
// Set the next filter ID to max(filter.ID) + 1
func updateUniqueFilterID(filters []FilterYAML) {
func updateUniqueFilterID(filters []filter) {
for _, filter := range filters {
if nextFilterID < filter.ID {
nextFilterID = filter.ID + 1
@@ -219,19 +238,22 @@ func assignUniqueFilterID() int64 {
}
// Sets up a timer that will be checking for filters updates periodically
func (d *DNSFilter) periodicallyRefreshFilters() {
func (f *Filtering) periodicallyRefreshFilters() {
const maxInterval = 1 * 60 * 60
intval := 5 // use a dynamically increasing time interval
for {
isNetErr, ok := false, false
if d.FiltersUpdateIntervalHours != 0 {
_, isNetErr, ok = d.tryRefreshFilters(true, true, false)
if ok && !isNetErr {
isNetworkErr := false
if config.DNS.FiltersUpdateIntervalHours != 0 && atomic.CompareAndSwapUint32(&f.refreshStatus, 0, 1) {
f.refreshLock.Lock()
_, isNetworkErr = f.refreshFiltersIfNecessary(filterRefreshBlocklists | filterRefreshAllowlists)
f.refreshLock.Unlock()
f.refreshStatus = 0
if !isNetworkErr {
intval = maxInterval
}
}
if isNetErr {
if isNetworkErr {
intval *= 2
if intval > maxInterval {
intval = maxInterval
@@ -242,73 +264,51 @@ func (d *DNSFilter) periodicallyRefreshFilters() {
}
}
// tryRefreshFilters is like [refreshFilters], but backs down if the update is
// already going on.
// Refresh filters
// flags: filterRefresh*
// important:
//
// TODO(e.burkov): Get rid of the concurrency pattern which requires the
// sync.Mutex.TryLock.
func (d *DNSFilter) tryRefreshFilters(block, allow, force bool) (updated int, isNetworkErr, ok bool) {
if ok = d.refreshLock.TryLock(); !ok {
return 0, false, ok
// TRUE: ignore the fact that we're currently updating the filters
func (f *Filtering) refreshFilters(flags int, important bool) (int, error) {
set := atomic.CompareAndSwapUint32(&f.refreshStatus, 0, 1)
if !important && !set {
return 0, fmt.Errorf("filters update procedure is already running")
}
defer d.refreshLock.Unlock()
updated, isNetworkErr = d.refreshFiltersIntl(block, allow, force)
return updated, isNetworkErr, ok
f.refreshLock.Lock()
nUpdated, _ := f.refreshFiltersIfNecessary(flags)
f.refreshLock.Unlock()
f.refreshStatus = 0
return nUpdated, nil
}
// refreshFilters updates the lists and returns the number of updated ones.
// It's safe for concurrent use, but blocks at least until the previous
// refreshing is finished.
func (d *DNSFilter) refreshFilters(block, allow, force bool) (updated int) {
d.refreshLock.Lock()
defer d.refreshLock.Unlock()
func (f *Filtering) refreshFiltersArray(filters *[]filter, force bool) (int, []filter, []bool, bool) {
var updateFilters []filter
var updateFlags []bool // 'true' if filter data has changed
updated, _ = d.refreshFiltersIntl(block, allow, force)
return updated
}
// listsToUpdate returns the slice of filter lists that could be updated.
func (d *DNSFilter) listsToUpdate(filters *[]FilterYAML, force bool) (toUpd []FilterYAML) {
now := time.Now()
d.filtersMu.RLock()
defer d.filtersMu.RUnlock()
config.RLock()
for i := range *filters {
flt := &(*filters)[i] // otherwise we will be operating on a copy
log.Debug("checking list at index %d: %v", i, flt)
f := &(*filters)[i] // otherwise we will be operating on a copy
if !flt.Enabled {
if !f.Enabled {
continue
}
if !force {
exp := flt.LastUpdated.Add(time.Duration(d.FiltersUpdateIntervalHours) * time.Hour)
if now.Before(exp) {
continue
}
expireTime := f.LastUpdated.Unix() + int64(config.DNS.FiltersUpdateIntervalHours)*60*60
if !force && expireTime > now.Unix() {
continue
}
toUpd = append(toUpd, FilterYAML{
Filter: Filter{
ID: flt.ID,
},
URL: flt.URL,
Name: flt.Name,
checksum: flt.checksum,
})
var uf filter
uf.ID = f.ID
uf.URL = f.URL
uf.Name = f.Name
uf.checksum = f.checksum
updateFilters = append(updateFilters, uf)
}
config.RUnlock()
return toUpd
}
func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int, []FilterYAML, []bool, bool) {
var updateFlags []bool // 'true' if filter data has changed
updateFilters := d.listsToUpdate(filters, force)
if len(updateFilters) == 0 {
return 0, nil, nil, false
}
@@ -316,7 +316,7 @@ func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int,
nfail := 0
for i := range updateFilters {
uf := &updateFilters[i]
updated, err := d.update(uf)
updated, err := f.update(uf)
updateFlags = append(updateFlags, updated)
if err != nil {
nfail++
@@ -334,7 +334,7 @@ func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int,
uf := &updateFilters[i]
updated := updateFlags[i]
d.filtersMu.Lock()
config.Lock()
for k := range *filters {
f := &(*filters)[k]
if f.ID != uf.ID || f.URL != uf.URL {
@@ -352,14 +352,20 @@ func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int,
f.checksum = uf.checksum
updateCount++
}
d.filtersMu.Unlock()
config.Unlock()
}
return updateCount, updateFilters, updateFlags, false
}
// refreshFiltersIntl checks filters and updates them if necessary. If force is
// true, it ignores the filter.LastUpdated field value.
const (
filterRefreshForce = 1 // ignore last file modification date
filterRefreshAllowlists = 2 // update allow-lists
filterRefreshBlocklists = 4 // update block-lists
)
// refreshFiltersIfNecessary checks filters and updates them if necessary. If
// force is true, it ignores the filter.LastUpdated field value.
//
// Algorithm:
//
@@ -372,49 +378,53 @@ func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int,
// that this method works only on Unix systems. On Windows, don't pass
// files to filtering, pass the whole data.
//
// refreshFiltersIntl returns the number of updated filters. It also returns
// true if there was a network error and nothing could be updated.
// refreshFiltersIfNecessary returns the number of updated filters. It also
// returns true if there was a network error and nothing could be updated.
//
// TODO(a.garipov, e.burkov): What the hell?
func (d *DNSFilter) refreshFiltersIntl(block, allow, force bool) (int, bool) {
log.Debug("filtering: updating...")
func (f *Filtering) refreshFiltersIfNecessary(flags int) (int, bool) {
log.Debug("Filters: updating...")
updNum := 0
var lists []FilterYAML
var toUpd []bool
isNetErr := false
if block {
updNum, lists, toUpd, isNetErr = d.refreshFiltersArray(&d.Filters, force)
updateCount := 0
var updateFilters []filter
var updateFlags []bool
netError := false
netErrorW := false
force := false
if (flags & filterRefreshForce) != 0 {
force = true
}
if allow {
updNumAl, listsAl, toUpdAl, isNetErrAl := d.refreshFiltersArray(&d.WhitelistFilters, force)
updNum += updNumAl
lists = append(lists, listsAl...)
toUpd = append(toUpd, toUpdAl...)
isNetErr = isNetErr || isNetErrAl
if (flags & filterRefreshBlocklists) != 0 {
updateCount, updateFilters, updateFlags, netError = f.refreshFiltersArray(&config.Filters, force)
}
if isNetErr {
if (flags & filterRefreshAllowlists) != 0 {
updateCountW := 0
var updateFiltersW []filter
var updateFlagsW []bool
updateCountW, updateFiltersW, updateFlagsW, netErrorW = f.refreshFiltersArray(&config.WhitelistFilters, force)
updateCount += updateCountW
updateFilters = append(updateFilters, updateFiltersW...)
updateFlags = append(updateFlags, updateFlagsW...)
}
if netError && netErrorW {
return 0, true
}
if updNum != 0 {
d.EnableFilters(false)
if updateCount != 0 {
enableFilters(false)
for i := range lists {
uf := &lists[i]
updated := toUpd[i]
for i := range updateFilters {
uf := &updateFilters[i]
updated := updateFlags[i]
if !updated {
continue
}
_ = os.Remove(uf.Path(d.DataDir) + ".old")
_ = os.Remove(uf.Path() + ".old")
}
}
log.Debug("filtering: update finished")
return updNum, false
log.Debug("Filters: update finished")
return updateCount, false
}
// Allows printable UTF-8 text with CR, LF, TAB characters
@@ -430,7 +440,7 @@ func isPrintableText(data []byte, len int) bool {
}
// A helper function that parses filter contents and returns a number of rules and a filter name (if there's any)
func (d *DNSFilter) parseFilterContents(file io.Reader) (int, uint32, string) {
func (f *Filtering) parseFilterContents(file io.Reader) (int, uint32, string) {
rulesCount := 0
name := ""
seenTitle := false
@@ -445,7 +455,7 @@ func (d *DNSFilter) parseFilterContents(file io.Reader) (int, uint32, string) {
if len(line) == 0 {
//
} else if line[0] == '!' {
m := d.filterTitleRegexp.FindAllStringSubmatch(line, -1)
m := f.filterTitleRegexp.FindAllStringSubmatch(line, -1)
if len(m) > 0 && len(m[0]) >= 2 && !seenTitle {
name = m[0][1]
seenTitle = true
@@ -466,11 +476,11 @@ func (d *DNSFilter) parseFilterContents(file io.Reader) (int, uint32, string) {
}
// Perform upgrade on a filter and update LastUpdated value
func (d *DNSFilter) update(filter *FilterYAML) (bool, error) {
b, err := d.updateIntl(filter)
func (f *Filtering) update(filter *filter) (bool, error) {
b, err := f.updateIntl(filter)
filter.LastUpdated = time.Now()
if !b {
e := os.Chtimes(filter.Path(d.DataDir), filter.LastUpdated, filter.LastUpdated)
e := os.Chtimes(filter.Path(), filter.LastUpdated, filter.LastUpdated)
if e != nil {
log.Error("os.Chtimes(): %v", e)
}
@@ -478,7 +488,7 @@ func (d *DNSFilter) update(filter *FilterYAML) (bool, error) {
return b, err
}
func (d *DNSFilter) read(reader io.Reader, tmpFile *os.File, filter *FilterYAML) (int, error) {
func (f *Filtering) read(reader io.Reader, tmpFile *os.File, filter *filter) (int, error) {
htmlTest := true
firstChunk := make([]byte, 4*1024)
firstChunkLen := 0
@@ -529,20 +539,20 @@ func (d *DNSFilter) read(reader io.Reader, tmpFile *os.File, filter *FilterYAML)
// finalizeUpdate closes and gets rid of temporary file f with filter's content
// according to updated. It also saves new values of flt's name, rules number
// and checksum if sucсeeded.
func (d *DNSFilter) finalizeUpdate(
file *os.File,
flt *FilterYAML,
func finalizeUpdate(
f *os.File,
flt *filter,
updated bool,
name string,
rnum int,
cs uint32,
) (err error) {
tmpFileName := file.Name()
tmpFileName := f.Name()
// Close the file before renaming it because it's required on Windows.
//
// See https://github.com/adguardTeam/adGuardHome/issues/1553.
if err = file.Close(); err != nil {
if err = f.Close(); err != nil {
return fmt.Errorf("closing temporary file: %w", err)
}
@@ -552,9 +562,9 @@ func (d *DNSFilter) finalizeUpdate(
return os.Remove(tmpFileName)
}
log.Printf("saving filter %d contents to: %s", flt.ID, flt.Path(d.DataDir))
log.Printf("saving filter %d contents to: %s", flt.ID, flt.Path())
if err = os.Rename(tmpFileName, flt.Path(d.DataDir)); err != nil {
if err = os.Rename(tmpFileName, flt.Path()); err != nil {
return errors.WithDeferred(err, os.Remove(tmpFileName))
}
@@ -568,12 +578,12 @@ func (d *DNSFilter) finalizeUpdate(
// processUpdate copies filter's content from src to dst and returns the name,
// rules number, and checksum for it. It also returns the number of bytes read
// from src.
func (d *DNSFilter) processUpdate(
func (f *Filtering) processUpdate(
src io.Reader,
dst *os.File,
flt *FilterYAML,
flt *filter,
) (name string, rnum int, cs uint32, n int, err error) {
if n, err = d.read(src, dst, flt); err != nil {
if n, err = f.read(src, dst, flt); err != nil {
return "", 0, 0, 0, err
}
@@ -581,14 +591,14 @@ func (d *DNSFilter) processUpdate(
return "", 0, 0, 0, err
}
rnum, cs, name = d.parseFilterContents(dst)
rnum, cs, name = f.parseFilterContents(dst)
return name, rnum, cs, n, nil
}
// updateIntl updates the flt rewriting it's actual file. It returns true if
// the actual update has been performed.
func (d *DNSFilter) updateIntl(flt *FilterYAML) (ok bool, err error) {
func (f *Filtering) updateIntl(flt *filter) (ok bool, err error) {
log.Tracef("downloading update for filter %d from %s", flt.ID, flt.URL)
var name string
@@ -596,12 +606,12 @@ func (d *DNSFilter) updateIntl(flt *FilterYAML) (ok bool, err error) {
var cs uint32
var tmpFile *os.File
tmpFile, err = os.CreateTemp(filepath.Join(d.DataDir, filterDir), "")
tmpFile, err = os.CreateTemp(filepath.Join(Context.getDataDir(), filterDir), "")
if err != nil {
return false, err
}
defer func() {
err = errors.WithDeferred(err, d.finalizeUpdate(tmpFile, flt, ok, name, rnum, cs))
err = errors.WithDeferred(err, finalizeUpdate(tmpFile, flt, ok, name, rnum, cs))
ok = ok && err == nil
if ok {
log.Printf("updated filter %d: %d bytes, %d rules", flt.ID, n, rnum)
@@ -628,7 +638,7 @@ func (d *DNSFilter) updateIntl(flt *FilterYAML) (ok bool, err error) {
r = file
} else {
var resp *http.Response
resp, err = d.HTTPClient.Get(flt.URL)
resp, err = Context.client.Get(flt.URL)
if err != nil {
log.Printf("requesting filter from %s, skip: %s", flt.URL, err)
@@ -645,16 +655,16 @@ func (d *DNSFilter) updateIntl(flt *FilterYAML) (ok bool, err error) {
r = resp.Body
}
name, rnum, cs, n, err = d.processUpdate(r, tmpFile, flt)
name, rnum, cs, n, err = f.processUpdate(r, tmpFile, flt)
return cs != flt.checksum, err
}
// loads filter contents from the file in dataDir
func (d *DNSFilter) load(filter *FilterYAML) (err error) {
filterFilePath := filter.Path(d.DataDir)
func (f *Filtering) load(filter *filter) (err error) {
filterFilePath := filter.Path()
log.Tracef("filtering: loading filter %d from %s", filter.ID, filterFilePath)
log.Tracef("filtering: loading filter %d contents to: %s", filter.ID, filterFilePath)
file, err := os.Open(filterFilePath)
if errors.Is(err, os.ErrNotExist) {
@@ -672,7 +682,7 @@ func (d *DNSFilter) load(filter *FilterYAML) (err error) {
log.Tracef("filtering: File %s, id %d, length %d", filterFilePath, filter.ID, st.Size())
rulesCount, checksum, _ := d.parseFilterContents(file)
rulesCount, checksum, _ := f.parseFilterContents(file)
filter.RulesCount = rulesCount
filter.checksum = checksum
@@ -681,45 +691,56 @@ func (d *DNSFilter) load(filter *FilterYAML) (err error) {
return nil
}
func (d *DNSFilter) EnableFilters(async bool) {
d.filtersMu.RLock()
defer d.filtersMu.RUnlock()
d.enableFiltersLocked(async)
// Clear filter rules
func (filter *filter) unload() {
filter.RulesCount = 0
filter.checksum = 0
}
func (d *DNSFilter) enableFiltersLocked(async bool) {
filters := []Filter{{
ID: CustomListID,
Data: []byte(strings.Join(d.UserRules, "\n")),
// Path to the filter contents
func (filter *filter) Path() string {
return filepath.Join(Context.getDataDir(), filterDir, strconv.FormatInt(filter.ID, 10)+".txt")
}
func enableFilters(async bool) {
config.RLock()
defer config.RUnlock()
enableFiltersLocked(async)
}
func enableFiltersLocked(async bool) {
filters := []filtering.Filter{{
ID: filtering.CustomListID,
Data: []byte(strings.Join(config.UserRules, "\n")),
}}
for _, filter := range d.Filters {
for _, filter := range config.Filters {
if !filter.Enabled {
continue
}
filters = append(filters, Filter{
filters = append(filters, filtering.Filter{
ID: filter.ID,
FilePath: filter.Path(d.DataDir),
FilePath: filter.Path(),
})
}
var allowFilters []Filter
for _, filter := range d.WhitelistFilters {
var allowFilters []filtering.Filter
for _, filter := range config.WhitelistFilters {
if !filter.Enabled {
continue
}
allowFilters = append(allowFilters, Filter{
allowFilters = append(allowFilters, filtering.Filter{
ID: filter.ID,
FilePath: filter.Path(d.DataDir),
FilePath: filter.Path(),
})
}
if err := d.SetFilters(filters, allowFilters, async); err != nil {
if err := Context.dnsFilter.SetFilters(filters, allowFilters, async); err != nil {
log.Debug("enabling filters: %s", err)
}
d.SetEnabled(d.FilteringEnabled)
Context.dnsFilter.SetEnabled(config.DNS.FilteringEnabled)
}

View File

@@ -1,4 +1,4 @@
package filtering
package home
import (
"io/fs"
@@ -51,17 +51,15 @@ func TestFilters(t *testing.T) {
l := testStartFilterListener(t, &fltContent)
tempDir := t.TempDir()
filters, err := New(&Config{
DataDir: tempDir,
HTTPClient: &http.Client{
Context = homeContext{
workDir: t.TempDir(),
client: &http.Client{
Timeout: 5 * time.Second,
},
}, nil)
require.NoError(t, err)
}
Context.filters.Init()
f := &FilterYAML{
f := &filter{
URL: (&url.URL{
Scheme: "http",
Host: (&netutil.IPPort{
@@ -73,22 +71,21 @@ func TestFilters(t *testing.T) {
}
updateAndAssert := func(t *testing.T, want require.BoolAssertionFunc, wantRulesCount int) {
var ok bool
ok, err = filters.update(f)
ok, err := Context.filters.update(f)
require.NoError(t, err)
want(t, ok)
assert.Equal(t, wantRulesCount, f.RulesCount)
var dir []fs.DirEntry
dir, err = os.ReadDir(filepath.Join(tempDir, filterDir))
dir, err = os.ReadDir(filepath.Join(Context.getDataDir(), filterDir))
require.NoError(t, err)
assert.Len(t, dir, 1)
require.FileExists(t, f.Path(tempDir))
require.FileExists(t, f.Path())
err = filters.load(f)
err = Context.filters.load(f)
require.NoError(t, err)
}
@@ -108,9 +105,11 @@ func TestFilters(t *testing.T) {
})
t.Run("load_unload", func(t *testing.T) {
err = filters.load(f)
err := Context.filters.load(f)
require.NoError(t, err)
f.unload()
})
require.NoError(t, os.Remove(f.Path()))
}

View File

@@ -10,7 +10,6 @@ import (
"net"
"net/http"
"net/http/pprof"
"net/netip"
"net/url"
"os"
"os/signal"
@@ -21,7 +20,6 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
@@ -35,7 +33,6 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"golang.org/x/exp/slices"
"gopkg.in/natefinch/lumberjack.v2"
)
@@ -55,9 +52,10 @@ type homeContext struct {
dnsServer *dnsforward.Server // DNS module
rdns *RDNS // rDNS module
whois *WHOIS // WHOIS module
dnsFilter *filtering.DNSFilter // DNS filtering module
dhcpServer dhcpd.Interface // DHCP module
auth *Auth // HTTP authentication module
filters *filtering.DNSFilter // DNS filtering module
filters Filtering // DNS filtering module
web *Web // Web (HTTP, HTTPS) module
tls *TLSMod // TLS module
// etcHosts is an IP-hostname pairs set taken from system configuration
@@ -142,12 +140,7 @@ func setupContext(args options) {
checkPermissions()
}
switch version.Channel() {
case version.ChannelEdge, version.ChannelDevelopment:
config.BetaBindPort = 3001
default:
// Go on.
}
initConfig()
Context.tlsRoots = LoadSystemRootCAs()
Context.transport = &http.Transport{
@@ -272,14 +265,6 @@ func setupHostsContainer() (err error) {
}
func setupConfig(args options) (err error) {
config.DNS.DnsfilterConf.EtcHosts = Context.etcHosts
config.DNS.DnsfilterConf.ConfigModified = onConfigModified
config.DNS.DnsfilterConf.HTTPRegister = httpRegister
config.DNS.DnsfilterConf.DataDir = Context.getDataDir()
config.DNS.DnsfilterConf.Filters = slices.Clone(config.Filters)
config.DNS.DnsfilterConf.WhitelistFilters = slices.Clone(config.WhitelistFilters)
config.DNS.DnsfilterConf.HTTPClient = Context.client
config.DHCP.WorkDir = Context.workDir
config.DHCP.HTTPRegister = httpRegister
config.DHCP.ConfigModified = onConfigModified
@@ -340,7 +325,7 @@ func setupConfig(args options) (err error) {
}
// override bind host/port from the console
if args.bindHost.IsValid() {
if args.bindHost != nil {
config.BindHost = args.bindHost
}
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
@@ -399,6 +384,8 @@ func fatalOnError(err error) {
// run configures and starts AdGuard Home.
func run(args options, clientBuildFS fs.FS) {
var err error
// configure config filename
initConfigFilename(args)
@@ -417,7 +404,7 @@ func run(args options, clientBuildFS fs.FS) {
setupContext(args)
err := configureOS(config)
err = configureOS(config)
fatalOnError(err)
// clients package uses filtering package's static data (filtering.BlockedSvcKnown()),
@@ -538,7 +525,7 @@ func checkPermissions() {
}
// We should check if AdGuard Home is able to bind to port 53
err := aghnet.CheckPort("tcp", netip.AddrPortFrom(aghnet.IPv4Localhost(), defaultPortDNS))
err := aghnet.CheckPort("tcp", net.IP{127, 0, 0, 1}, defaultPortDNS)
if err != nil {
if errors.Is(err, os.ErrPermission) {
log.Fatal(`Permission check failed.
@@ -776,12 +763,12 @@ func printHTTPAddresses(proto string) {
}
port := config.BindPort
if proto == aghhttp.SchemeHTTPS {
if proto == schemeHTTPS {
port = tlsConf.PortHTTPS
}
// TODO(e.burkov): Inspect and perhaps merge with the previous condition.
if proto == aghhttp.SchemeHTTPS && tlsConf.ServerName != "" {
if proto == schemeHTTPS && tlsConf.ServerName != "" {
printWebAddrs(proto, tlsConf.ServerName, tlsConf.PortHTTPS, 0)
return

View File

@@ -8,7 +8,6 @@ import (
"net/url"
"path"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
@@ -83,7 +82,7 @@ func encodeMobileConfig(d *dnsSettings, clientID string) ([]byte, error) {
case dnsProtoHTTPS:
dspName = fmt.Sprintf("%s DoH", d.ServerName)
u := &url.URL{
Scheme: aghhttp.SchemeHTTPS,
Scheme: schemeHTTPS,
Host: d.ServerName,
Path: path.Join("/dns-query", clientID),
}

View File

@@ -3,11 +3,12 @@ package home
import (
"bytes"
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"net/netip"
"testing"
"github.com/AdguardTeam/golibs/netutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"howett.net/plist"
@@ -27,7 +28,7 @@ func setupDNSIPs(t testing.TB) {
config = &configuration{
DNS: dnsConfig{
BindHosts: []netip.Addr{netip.IPv4Unspecified()},
BindHosts: []net.IP{netutil.IPv4Zero()},
Port: defaultPortDNS,
},
}

View File

@@ -2,7 +2,7 @@ package home
import (
"fmt"
"net/netip"
"net"
"os"
"strconv"
@@ -12,15 +12,15 @@ import (
// options passed from command-line arguments
type options struct {
verbose bool // is verbose logging enabled
configFilename string // path to the config file
workDir string // path to the working directory where we will store the filters data and the querylog
bindHost netip.Addr // host address to bind HTTP server on
bindPort int // port to serve HTTP pages on
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
pidFile string // File name to save PID to
checkConfig bool // Check configuration and exit
disableUpdate bool // If set, don't check for updates
verbose bool // is verbose logging enabled
configFilename string // path to the config file
workDir string // path to the working directory where we will store the filters data and the querylog
bindHost net.IP // host address to bind HTTP server on
bindPort int // port to serve HTTP pages on
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
pidFile string // File name to save PID to
checkConfig bool // Check configuration and exit
disableUpdate bool // If set, don't check for updates
// service control action (see service.ControlAction array + "status" command)
serviceControlAction string
@@ -60,8 +60,8 @@ type arg struct {
// against its zero value and return nil if the parameter value is
// zero otherwise they return a string slice of the parameter
func ipSliceOrNil(ip netip.Addr) []string {
if !ip.IsValid() {
func ipSliceOrNil(ip net.IP) []string {
if ip == nil {
return nil
}
@@ -113,7 +113,7 @@ var workDirArg = arg{
var hostArg = arg{
"Host address to bind HTTP server on.",
"host", "h",
func(o options, v string) (options, error) { o.bindHost, _ = netip.ParseAddr(v); return o, nil }, nil, nil,
func(o options, v string) (options, error) { o.bindHost = net.ParseIP(v); return o, nil }, nil, nil,
func(o options) []string { return ipSliceOrNil(o.bindHost) },
}

View File

@@ -2,7 +2,7 @@ package home
import (
"fmt"
"net/netip"
"net"
"testing"
"github.com/stretchr/testify/assert"
@@ -56,13 +56,11 @@ func TestParseWorkDir(t *testing.T) {
}
func TestParseBindHost(t *testing.T) {
wantAddr := netip.AddrFrom4([4]byte{1, 2, 3, 4})
assert.Zero(t, testParseOK(t).bindHost, "empty is not host")
assert.Equal(t, wantAddr, testParseOK(t, "-h", "1.2.3.4").bindHost, "-h is host")
assert.Nil(t, testParseOK(t).bindHost, "empty is not host")
assert.Equal(t, net.IPv4(1, 2, 3, 4), testParseOK(t, "-h", "1.2.3.4").bindHost, "-h is host")
testParseParamMissing(t, "-h")
assert.Equal(t, wantAddr, testParseOK(t, "--host", "1.2.3.4").bindHost, "--host is host")
assert.Equal(t, net.IPv4(1, 2, 3, 4), testParseOK(t, "--host", "1.2.3.4").bindHost, "--host is host")
testParseParamMissing(t, "--host")
}
@@ -151,7 +149,7 @@ func TestSerialize(t *testing.T) {
ss: []string{"-w", "path"},
}, {
name: "bind_host",
opts: options{bindHost: netip.AddrFrom4([4]byte{1, 2, 3, 4})},
opts: options{bindHost: net.IP{1, 2, 3, 4}},
ss: []string{"-h", "1.2.3.4"},
}, {
name: "bind_port",

View File

@@ -11,7 +11,6 @@ import (
"syscall"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/errors"
@@ -278,7 +277,7 @@ AdGuard Home is successfully installed and will automatically start on boot.
There are a few more things that must be configured before you can use it.
Click on the link below and follow the Installation Wizard steps to finish setup.
AdGuard Home is now available at the following addresses:`)
printHTTPAddresses(aghhttp.SchemeHTTP)
printHTTPAddresses(schemeHTTP)
}
}

View File

@@ -4,7 +4,6 @@ import (
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/stretchr/testify/assert"
@@ -161,7 +160,7 @@ func assertEqualExcept(t *testing.T, oldConf, newConf yobj, oldKeys, newKeys []s
}
func testDiskConf(schemaVersion int) (diskConf yobj) {
filters := []filtering.FilterYAML{{
filters := []filter{{
URL: "https://filters.adtidy.org/android/filters/111_optimized.txt",
Name: "Latvian filter",
RulesCount: 100,

View File

@@ -4,12 +4,11 @@ import (
"context"
"crypto/tls"
"io/fs"
"net"
"net/http"
"net/netip"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/golibs/errors"
@@ -20,6 +19,12 @@ import (
"golang.org/x/net/http2/h2c"
)
// HTTP scheme constants.
const (
schemeHTTP = "http"
schemeHTTPS = "https"
)
const (
// readTimeout is the maximum duration for reading the entire request,
// including the body.
@@ -35,7 +40,7 @@ type webConfig struct {
clientFS fs.FS
clientBetaFS fs.FS
BindHost netip.Addr
BindHost net.IP
BindPort int
BetaBindPort int
PortHTTPS int
@@ -114,11 +119,8 @@ func CreateWeb(conf *webConfig) *Web {
// WebCheckPortAvailable - check if port is available
// BUT: if we are already using this port, no need
func WebCheckPortAvailable(port int) bool {
if Context.web.httpsServer.server != nil {
return true
}
return aghnet.CheckPort("tcp", netip.AddrPortFrom(config.BindHost, uint16(port))) == nil
return Context.web.httpsServer.server != nil ||
aghnet.CheckPort("tcp", config.BindHost, port) == nil
}
// TLSConfigChanged updates the TLS configuration and restarts the HTTPS server
@@ -164,7 +166,7 @@ func (web *Web) Start() {
// this loop is used as an ability to change listening host and/or port
for !web.httpsServer.shutdown {
printHTTPAddresses(aghhttp.SchemeHTTP)
printHTTPAddresses(schemeHTTP)
errs := make(chan error, 2)
// Use an h2c handler to support unencrypted HTTP/2, e.g. for proxies.
@@ -284,7 +286,7 @@ func (web *Web) tlsServerLoop() {
WriteTimeout: web.conf.WriteTimeout,
}
printHTTPAddresses(aghhttp.SchemeHTTPS)
printHTTPAddresses(schemeHTTPS)
err := web.httpsServer.server.ListenAndServeTLS("", "")
if err != http.ErrServerClosed {
cleanupAlways()

View File

@@ -9,8 +9,8 @@ require (
github.com/kisielk/errcheck v1.6.2
github.com/kyoh86/looppointer v0.1.7
github.com/securego/gosec/v2 v2.13.1
golang.org/x/tools v0.1.13-0.20220921142454-16b974289fe5
golang.org/x/vuln v0.0.0-20220921153644-d9be10b6cc84
golang.org/x/tools v0.1.13-0.20220803210227-8b9a1fbdf5c3
golang.org/x/vuln v0.0.0-20220912202342-0ed43f12cb05
honnef.co/go/tools v0.3.3
mvdan.cc/gofumpt v0.3.1
mvdan.cc/unparam v0.0.0-20220831102321-2fc90a84c7ec
@@ -25,10 +25,10 @@ require (
github.com/kyoh86/nolint v0.0.1 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
golang.org/x/exp v0.0.0-20220921023135-46d9e7742f1e // indirect
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 // indirect
golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91 // indirect
golang.org/x/mod v0.6.0-dev.0.20220907135952-02c991387e35 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

View File

@@ -55,15 +55,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20220921023135-46d9e7742f1e h1:Ctm9yurWsg7aWwIpH9Bnap/IdSVxixymIb3MhiMEQQA=
golang.org/x/exp v0.0.0-20220921023135-46d9e7742f1e/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91 h1:Ic/qN6TEifvObMGQy72k0n1LlJr7DjWWEi+MOsDOiSk=
golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220907135952-02c991387e35 h1:CZP0Rbk/s1EIiUMx5DS2MhK2ct52xpQxqddVD0FmF+o=
golang.org/x/mod v0.6.0-dev.0.20220907135952-02c991387e35/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
@@ -86,8 +86,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho=
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -100,10 +100,10 @@ golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc
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.1.13-0.20220921142454-16b974289fe5 h1:o1LhIiY5L+hLK9DWqfFlilCrpZnw/s7WU4iCUkb/bao=
golang.org/x/tools v0.1.13-0.20220921142454-16b974289fe5/go.mod h1:VsjNM1dMo+Ofkp5d7y7fOdQZD8MTXSQ4w3EPk65AvKU=
golang.org/x/vuln v0.0.0-20220921153644-d9be10b6cc84 h1:L0qUjdplndgX880fozFRGC242wAtfsViyRXWGlpZQ54=
golang.org/x/vuln v0.0.0-20220921153644-d9be10b6cc84/go.mod h1:7tDfEDtOLlzHQRi4Yzfg5seVBSvouUIjyPzBx4q5CxQ=
golang.org/x/tools v0.1.13-0.20220803210227-8b9a1fbdf5c3 h1:aE4T3aJwdCNz+s35ScSQYUzeGu7BOLDHZ1bBHVurqqY=
golang.org/x/tools v0.1.13-0.20220803210227-8b9a1fbdf5c3/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/vuln v0.0.0-20220912202342-0ed43f12cb05 h1:NWQHMTdThZhCArzUbnu1Bh+l3LdwUfjZws+ivBR2sxM=
golang.org/x/vuln v0.0.0-20220912202342-0ed43f12cb05/go.mod h1:7tDfEDtOLlzHQRi4Yzfg5seVBSvouUIjyPzBx4q5CxQ=
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -219,7 +219,10 @@ exit_on_output gofumpt --extra -e -l .
"$GO" vet ./...
govulncheck ./...
# TODO(a.garipov): Reenable this once https://github.com/golang/go/issues/55035
# is fixed.
#
# govulncheck ./...
# Apply more lax standards to the code we haven't properly refactored yet.
gocyclo --over 17 ./internal/querylog/

View File

@@ -85,7 +85,11 @@ in
# num_commits_since_minor is the number of commits since the last new
# minor release. If the current commit is the new minor release,
# num_commits_since_minor is zero.
num_commits_since_minor="$( git rev-list --count "${last_minor_zero}..HEAD" )"
num_commits_since_minor="$( git rev-list "${last_minor_zero}..HEAD" | wc -l )"
# The output of darwin's implementation of wc needs to be trimmed from
# redundant spaces.
num_commits_since_minor="$( echo "$num_commits_since_minor" | tr -d '[:space:]' )"
readonly num_commits_since_minor
# next_minor is the next minor release version.