Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/src/__locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"blocking_mode_null_ip": "Null IP: Respond with zero IP address (0.0.0.0 for A; :: for AAAA)",
"blocking_mode_nxdomain": "NXDOMAIN: Respond with NXDOMAIN code",
"blocking_mode_refused": "REFUSED: Respond with REFUSED code",
"blocking_mode_noerror": "NOERROR: Respond with NOERROR code and an empty reply",
"blocklist": "Blocklist",
"bootstrap_dns": "Bootstrap DNS servers",
"bootstrap_dns_desc": "IP addresses of DNS servers used to resolve the hostnames of DoH/DoT upstream resolvers. Enter one IP address per line. Comments must start on a new line with #. Empty lines and comments are ignored.",
Expand Down Expand Up @@ -527,6 +528,7 @@
"no_servers_specified": "No servers specified",
"no_upstreams_data_found": "No upstreams data found",
"no_whitelist_added": "No allowlists added",
"noerror": "NOERROR",
"nothing_found": "Nothing found",
"null_ip": "Null IP",
"number_of_dns_query_blocked_24_hours": "The number of DNS requests blocked by adblock filters and hosts blocklists",
Expand Down
5 changes: 5 additions & 0 deletions client/src/components/Settings/Dns/Config/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
value: BLOCKING_MODES.nxdomain,
label: t('nxdomain'),
},
{
value: BLOCKING_MODES.noerror,
label: t('noerror'),
},
{
value: BLOCKING_MODES.null_ip,
label: t('null_ip'),
Expand All @@ -110,6 +114,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
t(`blocking_mode_default`),
t(`blocking_mode_refused`),
t(`blocking_mode_nxdomain`),
t(`blocking_mode_noerror`),
t(`blocking_mode_null_ip`),
t(`blocking_mode_custom_ip`),
];
Expand Down
1 change: 1 addition & 0 deletions client/src/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export const BLOCKING_MODES = {
default: 'default',
refused: 'refused',
nxdomain: 'nxdomain',
noerror: 'noerror',
null_ip: 'null_ip',
custom_ip: 'custom_ip',
};
Expand Down
2 changes: 2 additions & 0 deletions client_v2/src/__locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
"dns_blocking_mode_null_ip_desc": "Responds with 0.0.0.0 for A and :: for AAAA",
"dns_blocking_mode_nxdomain": "NXDOMAIN",
"dns_blocking_mode_nxdomain_desc": "Responds with NXDOMAIN code",
"dns_blocking_mode_noerror": "NOERROR",
"dns_blocking_mode_noerror_desc": "Responds with NOERROR code and an empty answer section",
"dns_blocking_mode_refused": "REFUSED",
"dns_blocking_mode_refused_desc": "Responds with REFUSED code",
"dns_blocking_mode_title": "Blocking mode",
Expand Down
26 changes: 26 additions & 0 deletions client_v2/src/__tests__/components/DnsSettings/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it, vi } from 'vitest';

vi.mock('panel/common/intl', () => ({
default: {
getMessage: vi.fn((key: string) => key),
},
}));

import {
getBlockingModeOptions,
getBlockingModeSummary,
} from 'panel/components/DnsSettings/helpers';
import { BLOCKING_MODES } from 'panel/helpers/constants';

describe('DNS settings helpers', () => {
it('exposes the NOERROR blocking mode', () => {
expect(getBlockingModeSummary(BLOCKING_MODES.noerror)).toBe('NOERROR');
expect(
getBlockingModeOptions().find(({ value }) => value === BLOCKING_MODES.noerror),
).toEqual({
text: 'dns_blocking_mode_noerror',
value: BLOCKING_MODES.noerror,
description: 'dns_blocking_mode_noerror_desc',
});
});
});
8 changes: 7 additions & 1 deletion client_v2/src/api/model/dNSConfigBlockingMode.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export type DNSConfigBlockingMode = 'default' | 'refused' | 'nxdomain' | 'null_ip' | 'custom_ip';
export type DNSConfigBlockingMode =
| 'default'
| 'refused'
| 'nxdomain'
| 'noerror'
| 'null_ip'
| 'custom_ip';
7 changes: 7 additions & 0 deletions client_v2/src/components/DnsSettings/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export const getBlockingModeSummary = (mode: DNSConfigBlockingMode): string => {
return 'REFUSED';
case BLOCKING_MODES.nxdomain:
return 'NXDOMAIN';
case BLOCKING_MODES.noerror:
return 'NOERROR';
case BLOCKING_MODES.null_ip:
return intl.getMessage('dns_blocking_mode_null_ip');
case BLOCKING_MODES.custom_ip:
Expand Down Expand Up @@ -81,6 +83,11 @@ export const getBlockingModeOptions = () => {
value: BLOCKING_MODES.nxdomain,
description: intl.getMessage('dns_blocking_mode_nxdomain_desc'),
},
{
text: intl.getMessage('dns_blocking_mode_noerror'),
value: BLOCKING_MODES.noerror,
description: intl.getMessage('dns_blocking_mode_noerror_desc'),
},
{
text: intl.getMessage('dns_blocking_mode_null_ip'),
value: BLOCKING_MODES.null_ip,
Expand Down
1 change: 1 addition & 0 deletions client_v2/src/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export const BLOCKING_MODES: { readonly [K in DNSConfigBlockingMode]: K } = {
default: 'default',
refused: 'refused',
nxdomain: 'nxdomain',
noerror: 'noerror',
null_ip: 'null_ip',
custom_ip: 'custom_ip',
};
Expand Down
1 change: 1 addition & 0 deletions internal/dnsforward/dnsforward.go
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ func validateBlockingMode(
filtering.BlockingModeDefault,
filtering.BlockingModeNXDOMAIN,
filtering.BlockingModeREFUSED,
filtering.BlockingModeNOERROR,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR adds filtering.BlockingModeNOERROR to the list of valid blocking modes, but I don't see a corresponding implementation in the filtering package. Make sure this mode is properly implemented in the filtering package to handle blocked domains with NOERROR responses without answer sections.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new approach to responding to filtered queries, not a new method for filtering.

filtering.BlockingModeNullIP:
return nil
case filtering.BlockingModeCustomIP:
Expand Down
61 changes: 61 additions & 0 deletions internal/dnsforward/dnsforward_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,67 @@ func TestBlockedRequest(t *testing.T) {
assert.True(t, reply.Answer[0].(*dns.A).A.IsUnspecified())
}

func TestBlockedRequestNOERROR(t *testing.T) {
const blockedResponseTTL uint32 = 42

forwardConf := ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
TLSConf: &TLSConfig{},
Config: Config{
UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
ClientsContainer: EmptyClientsContainer{},
},
ServePlainDNS: true,
}
s := createTestServer(
t,
&filtering.Config{
ProtectionEnabled: true,
BlockingMode: filtering.BlockingModeNOERROR,
BlockedResponseTTL: blockedResponseTTL,
},
forwardConf,
testTLSConfigProvider,
)
startDeferStop(t, s)

const host = "nxdomain.example.org."
wantSOA := &dns.SOA{
Hdr: dns.RR_Header{
Name: host,
Rrtype: dns.TypeSOA,
Class: dns.ClassINET,
Ttl: blockedResponseTTL,
Rdlength: 72,
},
Ns: "fake-for-negative-caching.adguard.com.",
Mbox: "hostmaster." + host,
Serial: 100500,
Refresh: 1800,
Retry: 900,
Expire: 604800,
Minttl: 86400,
}

addr := s.dnsProxy.Addr(proxy.ProtoUDP)
for _, qType := range []uint16{dns.TypeA, dns.TypeAAAA, dns.TypeHTTPS} {
t.Run(dns.TypeToString[qType], func(t *testing.T) {
req := createTestMessageWithType(host, qType)
reply, err := dns.Exchange(req, addr.String())
require.NoError(t, err)

assert.Equal(t, dns.RcodeSuccess, reply.Rcode)
assert.Empty(t, reply.Answer)
require.Len(t, reply.Ns, 1)
assert.Equal(t, wantSOA, reply.Ns[0])
})
}
}

func TestServerCustomClientUpstream(t *testing.T) {
const defaultCacheSize = 1024 * 1024

Expand Down
3 changes: 3 additions & 0 deletions internal/dnsforward/http_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
}, {
name: "blocking_mode_good",
wantSet: "",
}, {
name: "blocking_mode_noerror",
wantSet: "",
}, {
name: "blocking_mode_bad",
wantSet: "validating dns config: " +
Expand Down
10 changes: 10 additions & 0 deletions internal/dnsforward/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ func (s *Server) genForBlockingMode(
return s.NewMsgNXDOMAIN(req)
case filtering.BlockingModeREFUSED:
return s.makeResponseREFUSED(req)
case filtering.BlockingModeNOERROR:
return s.NewMsgNOERROR(req)
default:
s.logger.ErrorContext(ctx, "invalid blocking mode", "mode", mode)

Expand Down Expand Up @@ -402,6 +404,14 @@ func (s *Server) NewMsgNXDOMAIN(req *dns.Msg) (resp *dns.Msg) {
return resp
}

// NewMsgNOERROR creates an empty response pretending there is no address associated with the requested name.
func (s *Server) NewMsgNOERROR(req *dns.Msg) (resp *dns.Msg) {
resp = s.replyCompressed(req)
resp.Ns = s.genSOA(req)

return resp
}

// NewMsgSERVFAIL implements the [proxy.MessageConstructor] interface for
// *Server.
func (s *Server) NewMsgSERVFAIL(req *dns.Msg) (resp *dns.Msg) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,50 @@
"edns_cs_custom_ip": ""
}
},
"blocking_mode_noerror": {
"req": {
"blocking_mode": "noerror"
},
"want": {
"upstream_dns": [
"8.8.8.8:53",
"8.8.4.4:53"
],
"upstream_dns_file": "",
"bootstrap_dns": [
"9.9.9.10",
"149.112.112.10",
"2620:fe::10",
"2620:fe::fe:10"
],
"fallback_dns": [],
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "noerror",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"upstream_timeout": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
"upstream_mode": "",
"cache_size": 0,
"cache_ttl_min": 0,
"cache_ttl_max": 0,
"cache_enabled": false,
"cache_optimistic": false,
"resolve_clients": false,
"use_private_ptr_resolvers": false,
"local_ptr_upstreams": [],
"edns_cs_use_custom": false,
"edns_cs_custom_ip": ""
}
},
"blocking_mode_bad": {
"req": {
"blocking_mode": "custom_ip"
Expand Down
3 changes: 3 additions & 0 deletions internal/filtering/filtering.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ const (
// BlockingModeNXDOMAIN means respond with the NXDOMAIN code.
BlockingModeNXDOMAIN BlockingMode = "nxdomain"

// BlockingModeNOERROR means respond with the NOERROR code.
BlockingModeNOERROR BlockingMode = "noerror"

// BlockingModeREFUSED means respond with the REFUSED code.
BlockingModeREFUSED BlockingMode = "refused"
)
Expand Down
4 changes: 4 additions & 0 deletions openapi/next.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2140,13 +2140,17 @@

* `nxdomain`: Respond with the `NXDOMAIN` code.

* `noerror`: Respond with the `NOERROR` code and an empty answer
section.

* `refused`: Respond with the `REFUSET` code.

'enum':
- 'custom_ip'
- 'default'
- 'null_ip'
- 'nxdomain'
- 'noerror'
- 'refused'
'type': 'string'

Expand Down
1 change: 1 addition & 0 deletions openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,7 @@
- 'default'
- 'refused'
- 'nxdomain'
- 'noerror'
- 'null_ip'
- 'custom_ip'
'blocking_ipv4':
Expand Down
1 change: 1 addition & 0 deletions scripts/translations/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ func findUnused(fileNames []string, loc locales) (err error) {
knownUsed := []textLabel{
"blocking_mode_refused",
"blocking_mode_nxdomain",
"blocking_mode_noerror",
"blocking_mode_custom_ip",
}

Expand Down