Skip to content

Commit 1a3853d

Browse files
schzhnEugeneOne1
authored andcommitted
Pull request 2353: AGDNS-2688-check-host
Merge in DNS/adguard-home from AGDNS-2688-check-host to master Squashed commit of the following: commit bd9ed49 Merge: 8dffd94 c41af27 Author: Eugene Burkov <[email protected]> Date: Fri Mar 14 13:42:34 2025 +0300 Merge branch 'master' into AGDNS-2688-check-host commit 8dffd94 Author: Stanislav Chzhen <[email protected]> Date: Wed Mar 12 17:12:56 2025 +0300 filtering: imp code commit d9a01c8 Author: Stanislav Chzhen <[email protected]> Date: Tue Mar 11 20:33:18 2025 +0300 all: imp code commit f1aca5f Author: Ildar Kamalov <[email protected]> Date: Tue Mar 11 16:05:32 2025 +0300 ADG-9783 update check form commit a8ebb04 Author: Stanislav Chzhen <[email protected]> Date: Mon Mar 10 16:41:55 2025 +0300 dnsforward: imp docs commit 36f5db9 Merge: 9a746ee 66fba94 Author: Stanislav Chzhen <[email protected]> Date: Mon Mar 10 16:09:22 2025 +0300 Merge branch 'master' into AGDNS-2688-check-host commit 9a746ee Author: Stanislav Chzhen <[email protected]> Date: Mon Mar 10 16:06:48 2025 +0300 all: imp docs commit 0a25e1e Author: Stanislav Chzhen <[email protected]> Date: Thu Mar 6 21:48:44 2025 +0300 all: imp code commit ec618bc Author: Stanislav Chzhen <[email protected]> Date: Thu Mar 6 17:38:35 2025 +0300 all: imp code commit 979c5cf Author: Stanislav Chzhen <[email protected]> Date: Wed Mar 5 21:22:54 2025 +0300 all: add tests commit ce0d611 Author: Stanislav Chzhen <[email protected]> Date: Tue Mar 4 15:13:06 2025 +0300 all: check host
1 parent c41af27 commit 1a3853d

File tree

21 files changed

+467
-300
lines changed

21 files changed

+467
-300
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ NOTE: Add new changes BELOW THIS COMMENT.
2222

2323
- Go version has been updated to prevent the possibility of exploiting the Go vulnerabilities fixed in [1.24.1][go-1.24.1].
2424

25+
### Added
26+
27+
- The ability to check filtering rules for host names using an optional query type and optional ClientID or client IP address.
28+
- Optional `client` and `qtype` URL query parameters to the `GET /control/check_host` HTTP API.
29+
2530
### Fixed
2631

2732
- Clearing the DNS cache on the *DNS settings* page now includes both global cache and custom client cache.

client/src/__locales/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,10 @@
620620
"check_cname": "CNAME: {{cname}}",
621621
"check_reason": "Reason: {{reason}}",
622622
"check_service": "Service name: {{service}}",
623+
"check_hostname": "Hostname or domain name",
624+
"check_client_id": "Client identifier (ClientID or IP address)",
625+
"check_enter_client_id": "Enter client identifier",
626+
"check_dns_record": "Select DNS record type",
623627
"service_name": "Service name",
624628
"check_not_found": "Not found in your filter lists",
625629
"client_confirm_block": "Are you sure you want to block the client \"{{ip}}\"?",

client/src/components/Filters/Check/index.tsx

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import Info from './Info';
99
import { RootState } from '../../../initialState';
1010
import { validateRequiredValue } from '../../../helpers/validators';
1111
import { Input } from '../../ui/Controls/Input';
12+
import { DNS_RECORD_TYPES } from '../../../helpers/constants';
13+
import { Select } from '../../ui/Controls/Select';
1214

13-
interface FormValues {
15+
export type FilteringCheckFormValues = {
1416
name: string;
17+
client?: string;
18+
qtype?: string;
1519
}
1620

1721
type Props = {
18-
onSubmit?: (data: FormValues) => void;
22+
onSubmit?: (data: FilteringCheckFormValues) => void;
1923
};
2024

2125
const Check = ({ onSubmit }: Props) => {
@@ -27,11 +31,13 @@ const Check = ({ onSubmit }: Props) => {
2731
const {
2832
control,
2933
handleSubmit,
30-
formState: { isDirty, isValid },
31-
} = useForm<FormValues>({
34+
formState: { isValid },
35+
} = useForm<FilteringCheckFormValues>({
3236
mode: 'onBlur',
3337
defaultValues: {
3438
name: '',
39+
client: '',
40+
qtype: DNS_RECORD_TYPES[0],
3541
},
3642
});
3743

@@ -48,24 +54,56 @@ const Check = ({ onSubmit }: Props) => {
4854
<Input
4955
{...field}
5056
type="text"
57+
label={t('check_hostname')}
5158
data-testid="check_domain_name"
52-
placeholder={t('form_enter_host')}
59+
placeholder="example.com"
5360
error={fieldState.error?.message}
54-
rightAddon={
55-
<span className="input-group-append">
56-
<button
57-
className="btn btn-success btn-standard btn-large"
58-
type="submit"
59-
data-testid="check_domain_submit"
60-
disabled={!isDirty || !isValid || processingCheck}>
61-
{t('check')}
62-
</button>
63-
</span>
64-
}
6561
/>
6662
)}
6763
/>
6864

65+
<Controller
66+
name="client"
67+
control={control}
68+
render={({ field, fieldState }) => (
69+
<Input
70+
{...field}
71+
type="text"
72+
data-testid="check_client_id"
73+
label={t('check_client_id')}
74+
placeholder={t('check_enter_client_id')}
75+
error={fieldState.error?.message}
76+
/>
77+
)}
78+
/>
79+
80+
<Controller
81+
name="qtype"
82+
control={control}
83+
render={({ field }) => (
84+
<Select
85+
{...field}
86+
label={t('check_dns_record')}
87+
data-testid="check_dns_record_type"
88+
>
89+
{DNS_RECORD_TYPES.map((type) => (
90+
<option key={type} value={type}>
91+
{type}
92+
</option>
93+
))}
94+
</Select>
95+
)}
96+
/>
97+
98+
<button
99+
className="btn btn-success btn-standard btn-large"
100+
type="submit"
101+
data-testid="check_domain_submit"
102+
disabled={!isValid || processingCheck}
103+
>
104+
{t('check')}
105+
</button>
106+
69107
{hostname && (
70108
<>
71109
<hr />

client/src/components/Filters/CustomRules.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import PageTitle from '../ui/PageTitle';
77

88
import Examples from './Examples';
99

10-
import Check from './Check';
10+
import Check, { FilteringCheckFormValues } from './Check';
1111

1212
import { getTextareaCommentsHighlight, syncScroll } from '../../helpers/highlightTextareaComments';
1313
import { COMMENT_LINE_DEFAULT_TOKEN } from '../../helpers/constants';
@@ -48,8 +48,18 @@ class CustomRules extends Component<CustomRulesProps> {
4848
this.props.setRules(this.props.filtering.userRules);
4949
};
5050

51-
handleCheck = (values: any) => {
52-
this.props.checkHost(values);
51+
handleCheck = (values: FilteringCheckFormValues) => {
52+
const params: FilteringCheckFormValues = { name: values.name };
53+
54+
if (values.client) {
55+
params.client = values.client;
56+
}
57+
58+
if (values.qtype) {
59+
params.qtype = values.qtype;
60+
}
61+
62+
this.props.checkHost(params);
5363
};
5464

5565
onScroll = (e: any) => syncScroll(e, this.ref);

client/src/helpers/constants.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,3 +523,12 @@ export const TIME_UNITS = {
523523
HOURS: 'hours',
524524
DAYS: 'days',
525525
};
526+
527+
export const DNS_RECORD_TYPES = [
528+
"A", "AAAA", "AFSDB", "APL", "CAA", "CDNSKEY", "CDS", "CERT", "CNAME",
529+
"CSYNC", "DHCID", "DLV", "DNAME", "DNSKEY", "DS", "EUI48", "EUI64",
530+
"HINFO", "HIP", "HTTPS", "IPSECKEY", "KEY", "KX", "LOC", "MX", "NAPTR",
531+
"NS", "NSEC", "NSEC3", "NSEC3PARAM", "OPENPGPKEY", "PTR", "RP", "RRSIG",
532+
"SIG", "SMIMEA", "SOA", "SRV", "SSHFP", "SVCB", "TA", "TKEY",
533+
"TLSA", "TSIG", "TXT", "URI", "ZONEMD"
534+
];

internal/client/storage.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/AdguardTeam/AdGuardHome/internal/arpdb"
1414
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
15+
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
1516
"github.com/AdguardTeam/AdGuardHome/internal/whois"
1617
"github.com/AdguardTeam/dnsproxy/proxy"
1718
"github.com/AdguardTeam/golibs/errors"
@@ -671,3 +672,38 @@ func (s *Storage) ClearUpstreamCache() {
671672

672673
s.upstreamManager.clearUpstreamCache()
673674
}
675+
676+
// ApplyClientFiltering retrieves persistent client information using the
677+
// ClientID or client IP address, and applies it to the filtering settings.
678+
func (s *Storage) ApplyClientFiltering(id string, addr netip.Addr, setts *filtering.Settings) {
679+
c, ok := s.index.findByClientID(id)
680+
if !ok {
681+
c, ok = s.index.findByIP(addr)
682+
}
683+
684+
if !ok {
685+
s.logger.Debug("no client filtering settings found", "clientid", id, "addr", addr)
686+
687+
return
688+
}
689+
690+
s.logger.Debug("applying custom client filtering settings", "client_name", c.Name)
691+
692+
setts.ClientIP = addr
693+
694+
if c.UseOwnBlockedServices {
695+
setts.BlockedServices = c.BlockedServices.Clone()
696+
}
697+
698+
setts.ClientName = c.Name
699+
setts.ClientTags = slices.Clone(c.Tags)
700+
if !c.UseOwnSettings {
701+
return
702+
}
703+
704+
setts.FilteringEnabled = c.FilteringEnabled
705+
setts.SafeSearchEnabled = c.SafeSearchConf.Enabled
706+
setts.ClientSafeSearch = c.SafeSearch
707+
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
708+
setts.ParentalEnabled = c.ParentalEnabled
709+
}

internal/dnsforward/config.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
1717
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
1818
"github.com/AdguardTeam/AdGuardHome/internal/client"
19-
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
2019
"github.com/AdguardTeam/dnsproxy/proxy"
2120
"github.com/AdguardTeam/dnsproxy/upstream"
2221
"github.com/AdguardTeam/golibs/container"
@@ -34,9 +33,6 @@ import (
3433
type Config struct {
3534
// Callbacks for other modules
3635

37-
// FilterHandler is an optional additional filtering callback.
38-
FilterHandler func(cliAddr netip.Addr, clientID string, settings *filtering.Settings) `yaml:"-"`
39-
4036
// ClientsContainer stores the information about special handling of some
4137
// DNS clients.
4238
ClientsContainer ClientsContainer `yaml:"-"`

internal/dnsforward/dnsforward_internal_test.go

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
2828
"github.com/AdguardTeam/AdGuardHome/internal/filtering/hashprefix"
2929
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
30+
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
3031
"github.com/AdguardTeam/dnsproxy/proxy"
3132
"github.com/AdguardTeam/dnsproxy/upstream"
3233
"github.com/AdguardTeam/golibs/logutil/slogutil"
@@ -106,6 +107,21 @@ func startDeferStop(t *testing.T, s *Server) {
106107
testutil.CleanupAndRequireSuccess(t, s.Stop)
107108
}
108109

110+
// applyEmptyClientFiltering is a helper function for tests with
111+
// [filtering.Config] that does nothing.
112+
func applyEmptyClientFiltering(_ string, _ netip.Addr, _ *filtering.Settings) {}
113+
114+
// emptyFilteringBlockedServices is a helper function that returns an empty
115+
// filtering blocked services for tests.
116+
func emptyFilteringBlockedServices() (bsvc *filtering.BlockedServices) {
117+
return &filtering.BlockedServices{
118+
Schedule: schedule.EmptyWeekly(),
119+
}
120+
}
121+
122+
// createTestServer is a helper function that returns a properly initialized
123+
// *Server for use in tests, given the provided parameters. It also populates
124+
// the filtering configuration with default parameters.
109125
func createTestServer(
110126
t *testing.T,
111127
filterConf *filtering.Config,
@@ -123,6 +139,12 @@ func createTestServer(
123139
Data: []byte(rules),
124140
}}
125141

142+
filterConf.BlockedServices = cmp.Or(filterConf.BlockedServices, emptyFilteringBlockedServices())
143+
144+
if filterConf.ApplyClientFiltering == nil {
145+
filterConf.ApplyClientFiltering = applyEmptyClientFiltering
146+
}
147+
126148
f, err := filtering.New(filterConf, filters)
127149
require.NoError(t, err)
128150

@@ -926,9 +948,6 @@ func TestClientRulesForCNAMEMatching(t *testing.T) {
926948
UDPListenAddrs: []*net.UDPAddr{{}},
927949
TCPListenAddrs: []*net.TCPAddr{{}},
928950
Config: Config{
929-
FilterHandler: func(_ netip.Addr, _ string, settings *filtering.Settings) {
930-
settings.FilteringEnabled = false
931-
},
932951
UpstreamMode: UpstreamModeLoadBalance,
933952
EDNSClientSubnet: &EDNSClientSubnet{
934953
Enabled: false,
@@ -1020,10 +1039,12 @@ func TestBlockedCustomIP(t *testing.T) {
10201039
}}
10211040

10221041
f, err := filtering.New(&filtering.Config{
1023-
ProtectionEnabled: true,
1024-
BlockingMode: filtering.BlockingModeCustomIP,
1025-
BlockingIPv4: netip.Addr{},
1026-
BlockingIPv6: netip.Addr{},
1042+
ProtectionEnabled: true,
1043+
ApplyClientFiltering: applyEmptyClientFiltering,
1044+
BlockedServices: emptyFilteringBlockedServices(),
1045+
BlockingMode: filtering.BlockingModeCustomIP,
1046+
BlockingIPv4: netip.Addr{},
1047+
BlockingIPv6: netip.Addr{},
10271048
}, filters)
10281049
require.NoError(t, err)
10291050

@@ -1176,7 +1197,9 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
11761197

11771198
func TestRewrite(t *testing.T) {
11781199
c := &filtering.Config{
1179-
BlockingMode: filtering.BlockingModeDefault,
1200+
ApplyClientFiltering: applyEmptyClientFiltering,
1201+
BlockedServices: emptyFilteringBlockedServices(),
1202+
BlockingMode: filtering.BlockingModeDefault,
11801203
Rewrites: []*filtering.LegacyRewrite{{
11811204
Domain: "test.com",
11821205
Answer: "1.2.3.4",
@@ -1322,7 +1345,9 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
13221345
const localDomain = "lan"
13231346

13241347
flt, err := filtering.New(&filtering.Config{
1325-
BlockingMode: filtering.BlockingModeDefault,
1348+
ApplyClientFiltering: applyEmptyClientFiltering,
1349+
BlockedServices: emptyFilteringBlockedServices(),
1350+
BlockingMode: filtering.BlockingModeDefault,
13261351
}, nil)
13271352
require.NoError(t, err)
13281353

@@ -1411,8 +1436,10 @@ func TestPTRResponseFromHosts(t *testing.T) {
14111436
})
14121437

14131438
flt, err := filtering.New(&filtering.Config{
1414-
BlockingMode: filtering.BlockingModeDefault,
1415-
EtcHosts: hc,
1439+
ApplyClientFiltering: applyEmptyClientFiltering,
1440+
BlockedServices: emptyFilteringBlockedServices(),
1441+
BlockingMode: filtering.BlockingModeDefault,
1442+
EtcHosts: hc,
14161443
}, nil)
14171444
require.NoError(t, err)
14181445

internal/dnsforward/filter.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ import (
1717
func (s *Server) clientRequestFilteringSettings(dctx *dnsContext) (setts *filtering.Settings) {
1818
setts = s.dnsFilter.Settings()
1919
setts.ProtectionEnabled = dctx.protectionEnabled
20-
if s.conf.FilterHandler != nil {
21-
s.conf.FilterHandler(dctx.proxyCtx.Addr.Addr(), dctx.clientID, setts)
22-
}
20+
s.dnsFilter.ApplyAdditionalFiltering(dctx.proxyCtx.Addr.Addr(), dctx.clientID, setts)
2321

2422
return setts
2523
}

internal/dnsforward/filter_internal_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
4545
}}
4646

4747
f, err := filtering.New(&filtering.Config{
48-
ProtectionEnabled: true,
49-
BlockingMode: filtering.BlockingModeDefault,
48+
ProtectionEnabled: true,
49+
ApplyClientFiltering: applyEmptyClientFiltering,
50+
BlockedServices: emptyFilteringBlockedServices(),
51+
BlockingMode: filtering.BlockingModeDefault,
5052
}, filters)
5153
require.NoError(t, err)
5254
f.SetEnabled(true)

0 commit comments

Comments
 (0)