save_config
@@ -434,6 +452,7 @@ Form.propTypes = {
handleSubmit: PropTypes.func.isRequired,
handleChange: PropTypes.func,
isEnabled: PropTypes.bool.isRequired,
+ servePlainDns: PropTypes.bool.isRequired,
certificateChain: PropTypes.string.isRequired,
privateKey: PropTypes.string.isRequired,
certificatePath: PropTypes.string.isRequired,
@@ -467,6 +486,7 @@ const selector = formValueSelector(FORM_NAME.ENCRYPTION);
Form = connect((state) => {
const isEnabled = selector(state, 'enabled');
+ const servePlainDns = selector(state, 'serve_plain_dns');
const certificateChain = selector(state, 'certificate_chain');
const privateKey = selector(state, 'private_key');
const certificatePath = selector(state, 'certificate_path');
@@ -476,6 +496,7 @@ Form = connect((state) => {
const privateKeySaved = selector(state, 'private_key_saved');
return {
isEnabled,
+ servePlainDns,
certificateChain,
privateKey,
certificatePath,
diff --git a/client/src/components/Settings/Encryption/index.js b/client/src/components/Settings/Encryption/index.js
index 4e4cef67..bcf610c3 100644
--- a/client/src/components/Settings/Encryption/index.js
+++ b/client/src/components/Settings/Encryption/index.js
@@ -25,7 +25,8 @@ class Encryption extends Component {
handleFormChange = debounce((values) => {
const submitValues = this.getSubmitValues(values);
- if (submitValues.enabled) {
+
+ if (submitValues.enabled || submitValues.serve_plain_dns) {
this.props.validateTlsConfig(submitValues);
}
}, DEBOUNCE_TIMEOUT);
@@ -85,6 +86,7 @@ class Encryption extends Component {
certificate_path,
private_key_path,
private_key_saved,
+ serve_plain_dns,
} = encryption;
const initialValues = this.getInitialValues({
@@ -99,6 +101,7 @@ class Encryption extends Component {
certificate_path,
private_key_path,
private_key_saved,
+ serve_plain_dns,
});
return (
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index 0b345f4d..66454939 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -181,6 +181,7 @@ export const MODAL_TYPE = {
ADD_REWRITE: 'ADD_REWRITE',
EDIT_REWRITE: 'EDIT_REWRITE',
EDIT_LEASE: 'EDIT_LEASE',
+ ADD_LEASE: 'ADD_LEASE',
};
export const CLIENT_ID = {
@@ -435,7 +436,7 @@ export const SCHEME_TO_PROTOCOL_MAP = {
export const DNS_REQUEST_OPTIONS = {
PARALLEL: 'parallel',
FASTEST_ADDR: 'fastest_addr',
- LOAD_BALANCING: '',
+ LOAD_BALANCING: 'load_balance',
};
export const DHCP_FORM_NAMES = {
diff --git a/client/src/helpers/filters/filters.js b/client/src/helpers/filters/filters.js
index a1bf0555..b7e4a06a 100644
--- a/client/src/helpers/filters/filters.js
+++ b/client/src/helpers/filters/filters.js
@@ -142,11 +142,11 @@ export default {
"homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt"
},
- "adway_default_blocklist": {
- "name": "AdAway Default Blocklist",
+ "awavenue_ads_rule": {
+ "name": "AWAvenue Ads Rule",
"categoryId": "general",
- "homepage": "https://github.com/AdAway/adaway.github.io/",
- "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt"
+ "homepage": "https://awavenue.top/",
+ "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_53.txt"
},
"curben_phishing_filter": {
"name": "Phishing URL Blocklist (PhishTank and OpenPhish)",
@@ -190,6 +190,12 @@ export default {
"homepage": "https://github.com/hagezi/dns-blocklists#piracy",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_46.txt"
},
+ "hagezi_dyndns_blocklist": {
+ "name": "HaGeZi's DynDNS Blocklist",
+ "categoryId": "security",
+ "homepage": "https://github.com/hagezi/dns-blocklists",
+ "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_54.txt"
+ },
"hagezi_encrypted_dns_vpn_tor_proxy_bypass": {
"name": "HaGeZi's Encrypted DNS/VPN/TOR/Proxy Bypass",
"categoryId": "security",
diff --git a/client/src/helpers/form.js b/client/src/helpers/form.js
index f58aa830..1b7b4997 100644
--- a/client/src/helpers/form.js
+++ b/client/src/helpers/form.js
@@ -180,7 +180,7 @@ export const CheckboxField = ({
{!disabled
&& touched
&& error
- &&
{error} }
+ &&
{error}
}
>;
CheckboxField.propTypes = {
diff --git a/client/src/helpers/trackers/trackers.json b/client/src/helpers/trackers/trackers.json
index b5016dfb..247e5ce8 100644
--- a/client/src/helpers/trackers/trackers.json
+++ b/client/src/helpers/trackers/trackers.json
@@ -1,5 +1,5 @@
{
- "timeUpdated": "2023-12-01T15:24:07.522Z",
+ "timeUpdated": "2024-01-22T00:10:10.554Z",
"categories": {
"0": "audio_video_player",
"1": "comments",
@@ -10758,9 +10758,10 @@
},
"localytics": {
"name": "Localytics",
- "categoryId": 6,
- "url": "http://www.localytics.com/",
- "companyId": "localytics"
+ "categoryId": 101,
+ "url": "https://uplandsoftware.com/localytics/",
+ "companyId": "upland",
+ "source": "AdGuard"
},
"lockerdome": {
"name": "LockerDome",
@@ -15613,10 +15614,11 @@
"companyId": "sharecompany"
},
"sharepoint": {
- "name": "Microsoft SharePoint",
- "categoryId": 2,
- "url": "https://products.office.com/en-us/sharepoint/sharepoint-online-collaboration-software",
- "companyId": "microsoft"
+ "name": "SharePoint",
+ "categoryId": 8,
+ "url": "https://www.microsoft.com/microsoft-365/sharepoint/collaboration",
+ "companyId": "microsoft",
+ "source": "AdGuard"
},
"sharethis": {
"name": "ShareThis",
@@ -22958,6 +22960,7 @@
"loadercdn.com": "loadercdn.com",
"loadsource.org": "loadsource.org",
"web.localytics.com": "localytics",
+ "localytics.com": "localytics",
"cdn2.lockerdome.com": "lockerdome",
"addtoany.com": "lockerz_share",
"pixel.loganmedia.mobi": "logan_media",
@@ -23254,6 +23257,7 @@
"k-msedge.net": "msedge",
"l-msedge.net": "msedge",
"s-msedge.net": "msedge",
+ "spo-msedge.net": "msedge",
"t-msedge.net": "msedge",
"wac-msedge.net": "msedge",
"msn.com": "msn",
@@ -23521,8 +23525,10 @@
"outbrain.com": "outbrain",
"outbrainimg.com": "outbrain",
"live.com": "outlook",
+ "cloud.microsoft": "outlook",
"hotmail.com": "outlook",
"outlook.com": "outlook",
+ "svc.ms": "outlook",
"overheat.it": "overheat.it",
"oewabox.at": "owa",
"owneriq.net": "owneriq",
@@ -23790,6 +23796,7 @@
"rcsmediagroup.it": "rcs.it",
"d335luupugsy2.cloudfront.net": "rd_station",
"rea-group.com": "rea_group",
+ "reagroupdata.com.au": "rea_group",
"reastatic.net": "rea_group",
"d12ulf131zb0yj.cloudfront.net": "reachforce",
"reachforce.com": "reachforce",
@@ -24109,6 +24116,8 @@
"quintrics.nl": "sharecompany",
"sharecompany.nl": "sharecompany",
"sharepointonline.com": "sharepoint",
+ "onmicrosoft.com": "sharepoint",
+ "sharepoint.com": "sharepoint",
"sharethis.com": "sharethis",
"shareth.ru": "sharethrough",
"sharethrough.com": "sharethrough",
diff --git a/client/src/helpers/validators.js b/client/src/helpers/validators.js
index e9d100a8..76e79d6f 100644
--- a/client/src/helpers/validators.js
+++ b/client/src/helpers/validators.js
@@ -389,3 +389,18 @@ export const validateIPv6Subnet = (value) => {
}
return undefined;
};
+
+/**
+ * @returns {undefined|string}
+ * @param value
+ * @param allValues
+ */
+export const validatePlainDns = (value, allValues) => {
+ const { enabled } = allValues;
+
+ if (!enabled && !value) {
+ return 'encryption_plain_dns_error';
+ }
+
+ return undefined;
+};
diff --git a/client/src/reducers/dhcp.js b/client/src/reducers/dhcp.js
index 877451a5..47768b50 100644
--- a/client/src/reducers/dhcp.js
+++ b/client/src/reducers/dhcp.js
@@ -128,7 +128,8 @@ const dhcp = handleActions(
const newState = {
...state,
isModalOpen: !state.isModalOpen,
- leaseModalConfig: payload,
+ modalType: payload?.type || '',
+ leaseModalConfig: payload?.config,
};
return newState;
},
diff --git a/client/src/reducers/dnsConfig.js b/client/src/reducers/dnsConfig.js
index d877b6c4..93f64bd7 100644
--- a/client/src/reducers/dnsConfig.js
+++ b/client/src/reducers/dnsConfig.js
@@ -1,7 +1,7 @@
import { handleActions } from 'redux-actions';
import * as actions from '../actions/dnsConfig';
-import { ALL_INTERFACES_IP, BLOCKING_MODES } from '../helpers/constants';
+import { ALL_INTERFACES_IP, BLOCKING_MODES, DNS_REQUEST_OPTIONS } from '../helpers/constants';
const DEFAULT_BLOCKING_IPV4 = ALL_INTERFACES_IP;
const DEFAULT_BLOCKING_IPV6 = '::';
@@ -15,6 +15,7 @@ const dnsConfig = handleActions(
blocking_ipv4,
blocking_ipv6,
upstream_dns,
+ upstream_mode,
fallback_dns,
bootstrap_dns,
local_ptr_upstreams,
@@ -33,6 +34,7 @@ const dnsConfig = handleActions(
local_ptr_upstreams: (local_ptr_upstreams && local_ptr_upstreams.join('\n')) || '',
ratelimit_whitelist: (ratelimit_whitelist && ratelimit_whitelist.join('\n')) || '',
processingGetConfig: false,
+ upstream_mode: upstream_mode === '' ? DNS_REQUEST_OPTIONS.LOAD_BALANCING : upstream_mode,
};
},
diff --git a/client/src/reducers/encryption.js b/client/src/reducers/encryption.js
index 8fe9a2cb..6b04a49a 100644
--- a/client/src/reducers/encryption.js
+++ b/client/src/reducers/encryption.js
@@ -62,6 +62,7 @@ const encryption = handleActions({
processingConfig: false,
processingValidate: false,
enabled: false,
+ serve_plain_dns: false,
dns_names: null,
force_https: false,
issuer: '',
diff --git a/go.mod b/go.mod
index 990ef309..b7227ec9 100644
--- a/go.mod
+++ b/go.mod
@@ -3,12 +3,13 @@ module github.com/AdguardTeam/AdGuardHome
go 1.20
require (
- github.com/AdguardTeam/dnsproxy v0.60.1
- github.com/AdguardTeam/golibs v0.18.0
+ github.com/AdguardTeam/dnsproxy v0.63.1
+ github.com/AdguardTeam/golibs v0.19.0
github.com/AdguardTeam/urlfilter v0.17.3
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.2.7
github.com/bluele/gcache v0.0.2
+ github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
github.com/digineo/go-ipset/v2 v2.2.1
github.com/dimfeld/httptreemux/v5 v5.5.0
github.com/fsnotify/fsnotify v1.7.0
@@ -16,7 +17,7 @@ require (
github.com/google/go-cmp v0.6.0
github.com/google/gopacket v1.1.19
github.com/google/renameio/v2 v2.0.0
- github.com/google/uuid v1.4.0
+ github.com/google/uuid v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/kardianos/service v1.2.2
@@ -26,15 +27,15 @@ require (
// TODO(a.garipov): This package is deprecated; find a new one or use our
// own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0
- github.com/miekg/dns v1.1.57
- github.com/quic-go/quic-go v0.40.0
+ github.com/miekg/dns v1.1.58
+ github.com/quic-go/quic-go v0.40.1
github.com/stretchr/testify v1.8.4
github.com/ti-mo/netfilter v0.5.1
go.etcd.io/bbolt v1.3.8
- golang.org/x/crypto v0.16.0
- golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
- golang.org/x/net v0.19.0
- golang.org/x/sys v0.15.0
+ golang.org/x/crypto v0.18.0
+ golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
+ golang.org/x/net v0.20.0
+ golang.org/x/sys v0.16.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.1
@@ -47,20 +48,21 @@ require (
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
- github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 // indirect
+ github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 // indirect
// TODO(a.garipov): Upgrade to v0.5.0 once we switch to Go 1.21+.
- github.com/mdlayher/socket v0.4.1 // indirect
- github.com/onsi/ginkgo/v2 v2.13.2 // indirect
+ github.com/mdlayher/socket v0.5.0 // indirect
+ github.com/onsi/ginkgo/v2 v2.15.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
- github.com/pierrec/lz4/v4 v4.1.19 // indirect
+ github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
- github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
- go.uber.org/mock v0.3.0 // indirect
+ github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
+ go.uber.org/mock v0.4.0 // indirect
golang.org/x/mod v0.14.0 // indirect
- golang.org/x/sync v0.5.0 // indirect
+ golang.org/x/sync v0.6.0 // indirect
golang.org/x/text v0.14.0 // indirect
- golang.org/x/tools v0.16.0 // indirect
+ golang.org/x/tools v0.17.0 // indirect
+ gonum.org/v1/gonum v0.14.0 // indirect
)
diff --git a/go.sum b/go.sum
index d33b4e08..0c2a46dd 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
-github.com/AdguardTeam/dnsproxy v0.60.1 h1:YveGe7UZLaAiePkaV3orkc0IIfPX9vi/qQDIFdeO//A=
-github.com/AdguardTeam/dnsproxy v0.60.1/go.mod h1:B7FvvTFQZBfey1cJXQo732EyCLX6xj4JqrciCawATzg=
-github.com/AdguardTeam/golibs v0.18.0 h1:ckS2YK7t2Ub6UkXl0fnreVaM15Zb07Hh1gmFqttjpWg=
-github.com/AdguardTeam/golibs v0.18.0/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
+github.com/AdguardTeam/dnsproxy v0.63.1 h1:CilxSuLYcuYpbPCGB7w41UUqWRMu3dvj4c9TvkIrpBg=
+github.com/AdguardTeam/dnsproxy v0.63.1/go.mod h1:dRRAFOjrq4QYM92jGs4lt4BoY0Dm3EY3HkaleoM2Feo=
+github.com/AdguardTeam/golibs v0.19.0 h1:y/x+Xn3pDg1ZfQ+QEZapPJqaeVYUIMp/EODMtVhn7PM=
+github.com/AdguardTeam/golibs v0.19.0/go.mod h1:3WunclLLfrVAq7fYQRhd6f168FHOEMssnipVXCxDL/w=
github.com/AdguardTeam/urlfilter v0.17.3 h1:fg/ObbnO0Cv6aw0tW6N/ETDMhhNvmcUUOZ7HlmKC3rw=
github.com/AdguardTeam/urlfilter v0.17.3/go.mod h1:Jru7jFfeH2CoDf150uDs+rRYcZBzHHBz05r9REyDKyE=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
@@ -18,6 +18,8 @@ github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG+
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
+github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
+github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,13 +43,13 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
-github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 h1:PxlBVtIFHR/mtWk2i0gTEdCz+jBnqiuHNSki0epDbVs=
-github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
+github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
+github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
-github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
+github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
@@ -71,19 +73,19 @@ github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU
github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y=
github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg=
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
-github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
-github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
-github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
-github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
+github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
+github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
+github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
+github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
-github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
-github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
+github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
+github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
+github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
-github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4=
-github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
+github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -94,8 +96,8 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
-github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
-github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
+github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
+github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -109,19 +111,19 @@ github.com/ti-mo/netfilter v0.5.1 h1:cqamEd1c1zmpfpqvInLOro0Znq/RAfw2QL5wL2rAR/8
github.com/ti-mo/netfilter v0.5.1/go.mod h1:h9UPQ3ZrTZGBitay+LETMxZvNgWGK/efTUcqES2YiLw=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
-github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
-github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
+github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
+github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
-go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
-go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
+go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
+go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
-golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
-golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
+golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
+golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
+golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
@@ -132,12 +134,12 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
-golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
-golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -149,8 +151,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
@@ -158,10 +160,12 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
-golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
+golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
+golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
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=
+gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=
+gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
diff --git a/internal/aghalg/ringbuffer.go b/internal/aghalg/ringbuffer.go
index b97bcc67..3faa8cb5 100644
--- a/internal/aghalg/ringbuffer.go
+++ b/internal/aghalg/ringbuffer.go
@@ -1,23 +1,15 @@
package aghalg
-import (
- "github.com/AdguardTeam/golibs/errors"
-)
-
// RingBuffer is the implementation of ring buffer data structure.
type RingBuffer[T any] struct {
buf []T
- cur int
+ cur uint
full bool
}
// NewRingBuffer initializes the new instance of ring buffer. size must be
// greater or equal to zero.
-func NewRingBuffer[T any](size int) (rb *RingBuffer[T]) {
- if size < 0 {
- panic(errors.Error("ring buffer: size must be greater or equal to zero"))
- }
-
+func NewRingBuffer[T any](size uint) (rb *RingBuffer[T]) {
return &RingBuffer[T]{
buf: make([]T, size),
}
@@ -30,7 +22,7 @@ func (rb *RingBuffer[T]) Append(e T) {
}
rb.buf[rb.cur] = e
- rb.cur = (rb.cur + 1) % cap(rb.buf)
+ rb.cur = (rb.cur + 1) % uint(cap(rb.buf))
if rb.cur == 0 {
rb.full = true
}
@@ -87,12 +79,12 @@ func (rb *RingBuffer[T]) splitCur() (before, after []T) {
}
// Len returns a length of the buffer.
-func (rb *RingBuffer[T]) Len() (l int) {
+func (rb *RingBuffer[T]) Len() (l uint) {
if !rb.full {
return rb.cur
}
- return cap(rb.buf)
+ return uint(cap(rb.buf))
}
// Clear clears the buffer.
diff --git a/internal/aghalg/ringbuffer_test.go b/internal/aghalg/ringbuffer_test.go
index 43fbb46b..0209d27f 100644
--- a/internal/aghalg/ringbuffer_test.go
+++ b/internal/aghalg/ringbuffer_test.go
@@ -9,13 +9,13 @@ import (
)
// elements is a helper function that returns n elements of the buffer.
-func elements(b *aghalg.RingBuffer[int], n int, reverse bool) (es []int) {
+func elements(b *aghalg.RingBuffer[int], n uint, reverse bool) (es []int) {
fn := b.Range
if reverse {
fn = b.ReverseRange
}
- i := 0
+ var i uint
fn(func(e int) (cont bool) {
if i >= n {
return false
@@ -42,19 +42,14 @@ func TestNewRingBuffer(t *testing.T) {
assert.Zero(t, b.Len())
})
- t.Run("negative_size", func(t *testing.T) {
- assert.PanicsWithError(t, "ring buffer: size must be greater or equal to zero", func() {
- aghalg.NewRingBuffer[int](-5)
- })
- })
-
t.Run("zero", func(t *testing.T) {
b := aghalg.NewRingBuffer[int](0)
for i := 0; i < 10; i++ {
b.Append(i)
- assert.Equal(t, 0, b.Len())
- assert.Empty(t, elements(b, b.Len(), false))
- assert.Empty(t, elements(b, b.Len(), true))
+ bufLen := b.Len()
+ assert.EqualValues(t, 0, bufLen)
+ assert.Empty(t, elements(b, bufLen, false))
+ assert.Empty(t, elements(b, bufLen, true))
}
})
@@ -62,9 +57,10 @@ func TestNewRingBuffer(t *testing.T) {
b := aghalg.NewRingBuffer[int](1)
for i := 0; i < 10; i++ {
b.Append(i)
- assert.Equal(t, 1, b.Len())
- assert.Equal(t, []int{i}, elements(b, b.Len(), false))
- assert.Equal(t, []int{i}, elements(b, b.Len(), true))
+ bufLen := b.Len()
+ assert.EqualValues(t, 1, bufLen)
+ assert.Equal(t, []int{i}, elements(b, bufLen, false))
+ assert.Equal(t, []int{i}, elements(b, bufLen, true))
}
})
}
@@ -78,7 +74,7 @@ func TestRingBuffer_Range(t *testing.T) {
name string
want []int
count int
- length int
+ length uint
}{{
name: "three",
count: 3,
@@ -163,11 +159,11 @@ func TestRingBuffer_Range_increment(t *testing.T) {
for i, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b.Append(i)
-
- assert.Equal(t, tc.want, elements(b, b.Len(), false))
+ bufLen := b.Len()
+ assert.Equal(t, tc.want, elements(b, bufLen, false))
slices.Reverse(tc.want)
- assert.Equal(t, tc.want, elements(b, b.Len(), true))
+ assert.Equal(t, tc.want, elements(b, bufLen, true))
})
}
}
diff --git a/internal/aghchan/aghchan.go b/internal/aghchan/aghchan.go
deleted file mode 100644
index 1da1790a..00000000
--- a/internal/aghchan/aghchan.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Package aghchan contains channel utilities.
-package aghchan
-
-import (
- "fmt"
- "time"
-)
-
-// Receive returns an error if it cannot receive a value form c before timeout
-// runs out.
-func Receive[T any](c <-chan T, timeout time.Duration) (v T, ok bool, err error) {
- var zero T
- timeoutCh := time.After(timeout)
- select {
- case <-timeoutCh:
- // TODO(a.garipov): Consider implementing [errors.Aser] for
- // os.ErrTimeout.
- return zero, false, fmt.Errorf("did not receive after %s", timeout)
- case v, ok = <-c:
- return v, ok, nil
- }
-}
-
-// MustReceive panics if it cannot receive a value form c before timeout runs
-// out.
-func MustReceive[T any](c <-chan T, timeout time.Duration) (v T, ok bool) {
- v, ok, err := Receive(c, timeout)
- if err != nil {
- panic(err)
- }
-
- return v, ok
-}
diff --git a/internal/aghnet/hostscontainer.go b/internal/aghnet/hostscontainer.go
index e27b115f..76b61b03 100644
--- a/internal/aghnet/hostscontainer.go
+++ b/internal/aghnet/hostscontainer.go
@@ -1,65 +1,23 @@
package aghnet
import (
- "context"
"fmt"
"io"
"io/fs"
"net/netip"
"path"
- "strings"
"sync/atomic"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
- "github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/log"
- "golang.org/x/exp/maps"
- "golang.org/x/exp/slices"
)
-// DefaultHostsPaths returns the slice of paths default for the operating system
-// to files and directories which are containing the hosts database. The result
-// is intended to be used within fs.FS so the initial slash is omitted.
-func DefaultHostsPaths() (paths []string) {
- return defaultHostsPaths()
-}
-
-// MatchAddr returns the records for the IP address.
-func (hc *HostsContainer) MatchAddr(ip netip.Addr) (recs []*hostsfile.Record) {
- cur := hc.current.Load()
- if cur == nil {
- return nil
- }
-
- return cur.addrs[ip]
-}
-
-// MatchName returns the records for the hostname.
-func (hc *HostsContainer) MatchName(name string) (recs []*hostsfile.Record) {
- cur := hc.current.Load()
- if cur != nil {
- recs = cur.names[name]
- }
-
- return recs
-}
-
// hostsContainerPrefix is a prefix for logging and wrapping errors in
// HostsContainer's methods.
const hostsContainerPrefix = "hosts container"
-// Hosts is a map of IP addresses to the records, as it primarily stored in the
-// [HostsContainer]. It should not be accessed for writing since it may be read
-// concurrently, users should clone it before modifying.
-//
-// The order of records for each address is preserved from original files, but
-// the order of the addresses, being a map key, is not.
-//
-// TODO(e.burkov): Probably, this should be a sorted slice of records.
-type Hosts map[netip.Addr][]*hostsfile.Record
-
// HostsContainer stores the relevant hosts database provided by the OS and
// processes both A/AAAA and PTR DNS requests for those.
type HostsContainer struct {
@@ -67,10 +25,10 @@ type HostsContainer struct {
done chan struct{}
// updates is the channel for receiving updated hosts.
- updates chan Hosts
+ updates chan *hostsfile.DefaultStorage
// current is the last set of hosts parsed.
- current atomic.Pointer[hostsIndex]
+ current atomic.Pointer[hostsfile.DefaultStorage]
// fsys is the working file system to read hosts files from.
fsys fs.FS
@@ -111,7 +69,7 @@ func NewHostsContainer(
hc = &HostsContainer{
done: make(chan struct{}, 1),
- updates: make(chan Hosts, 1),
+ updates: make(chan *hostsfile.DefaultStorage, 1),
fsys: fsys,
watcher: w,
patterns: patterns,
@@ -152,11 +110,25 @@ func (hc *HostsContainer) Close() (err error) {
return err
}
-// Upd returns the channel into which the updates are sent.
-func (hc *HostsContainer) Upd() (updates <-chan Hosts) {
+// Upd returns the channel into which the updates are sent. The updates
+// themselves must not be modified.
+func (hc *HostsContainer) Upd() (updates <-chan *hostsfile.DefaultStorage) {
return hc.updates
}
+// type check
+var _ hostsfile.Storage = (*HostsContainer)(nil)
+
+// ByAddr implements the [hostsfile.Storage] interface for *HostsContainer.
+func (hc *HostsContainer) ByAddr(addr netip.Addr) (names []string) {
+ return hc.current.Load().ByAddr(addr)
+}
+
+// ByName implements the [hostsfile.Storage] interface for *HostsContainer.
+func (hc *HostsContainer) ByName(name string) (addrs []netip.Addr) {
+ return hc.current.Load().ByName(name)
+}
+
// pathsToPatterns converts paths into patterns compatible with fs.Glob.
func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error) {
for i, p := range paths {
@@ -167,7 +139,7 @@ func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error)
continue
}
- // Don't put a filename here since it's already added by fs.Stat.
+ // Don't put a filename here since it's already added by [fs.Stat].
return nil, fmt.Errorf("path at index %d: %w", i, err)
}
@@ -209,7 +181,7 @@ func (hc *HostsContainer) handleEvents() {
}
// sendUpd tries to send the parsed data to the ch.
-func (hc *HostsContainer) sendUpd(recs Hosts) {
+func (hc *HostsContainer) sendUpd(recs *hostsfile.DefaultStorage) {
log.Debug("%s: sending upd", hostsContainerPrefix)
ch := hc.updates
@@ -226,67 +198,6 @@ func (hc *HostsContainer) sendUpd(recs Hosts) {
}
}
-// hostsIndex is a [hostsfile.Set] to enumerate all the records.
-type hostsIndex struct {
- // addrs maps IP addresses to the records.
- addrs Hosts
-
- // names maps hostnames to the records.
- names map[string][]*hostsfile.Record
-}
-
-// walk is a file walking function for hostsIndex.
-func (idx *hostsIndex) walk(r io.Reader) (patterns []string, cont bool, err error) {
- return nil, true, hostsfile.Parse(idx, r, nil)
-}
-
-// type check
-var _ hostsfile.Set = (*hostsIndex)(nil)
-
-// Add implements the [hostsfile.Set] interface for *hostsIndex.
-func (idx *hostsIndex) Add(rec *hostsfile.Record) {
- idx.addrs[rec.Addr] = append(idx.addrs[rec.Addr], rec)
- for _, name := range rec.Names {
- idx.names[name] = append(idx.names[name], rec)
- }
-}
-
-// type check
-var _ hostsfile.HandleSet = (*hostsIndex)(nil)
-
-// HandleInvalid implements the [hostsfile.HandleSet] interface for *hostsIndex.
-func (idx *hostsIndex) HandleInvalid(src string, _ []byte, err error) {
- lineErr := &hostsfile.LineError{}
- if !errors.As(err, &lineErr) {
- // Must not happen if idx passed to [hostsfile.Parse].
- return
- } else if errors.Is(lineErr, hostsfile.ErrEmptyLine) {
- // Ignore empty lines.
- return
- }
-
- log.Info("%s: warning: parsing %q: %s", hostsContainerPrefix, src, lineErr)
-}
-
-// equalRecs is an equality function for [*hostsfile.Record].
-func equalRecs(a, b *hostsfile.Record) (ok bool) {
- return a.Addr == b.Addr && a.Source == b.Source && slices.Equal(a.Names, b.Names)
-}
-
-// equalRecSlices is an equality function for slices of [*hostsfile.Record].
-func equalRecSlices(a, b []*hostsfile.Record) (ok bool) { return slices.EqualFunc(a, b, equalRecs) }
-
-// Equal returns true if indexes are equal.
-func (idx *hostsIndex) Equal(other *hostsIndex) (ok bool) {
- if idx == nil {
- return other == nil
- } else if other == nil {
- return false
- }
-
- return maps.EqualFunc(idx.addrs, other.addrs, equalRecSlices)
-}
-
// refresh gets the data from specified files and propagates the updates if
// needed.
//
@@ -294,63 +205,22 @@ func (idx *hostsIndex) Equal(other *hostsIndex) (ok bool) {
func (hc *HostsContainer) refresh() (err error) {
log.Debug("%s: refreshing", hostsContainerPrefix)
- var addrLen, nameLen int
- last := hc.current.Load()
- if last != nil {
- addrLen, nameLen = len(last.addrs), len(last.names)
- }
- idx := &hostsIndex{
- addrs: make(Hosts, addrLen),
- names: make(map[string][]*hostsfile.Record, nameLen),
- }
-
- _, err = aghos.FileWalker(idx.walk).Walk(hc.fsys, hc.patterns...)
+ // The error is always nil here since no readers passed.
+ strg, _ := hostsfile.NewDefaultStorage()
+ _, err = aghos.FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) {
+ // Don't wrap the error since it's already informative enough as is.
+ return nil, true, hostsfile.Parse(strg, r, nil)
+ }).Walk(hc.fsys, hc.patterns...)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
- // TODO(e.burkov): Serialize updates using time.
- if !last.Equal(idx) {
- hc.current.Store(idx)
- hc.sendUpd(idx.addrs)
+ // TODO(e.burkov): Serialize updates using [time.Time].
+ if !hc.current.Load().Equal(strg) {
+ hc.current.Store(strg)
+ hc.sendUpd(strg)
}
return nil
}
-
-// type check
-var _ upstream.Resolver = (*HostsContainer)(nil)
-
-// LookupNetIP implements the [upstream.Resolver] interface for *HostsContainer.
-func (hc *HostsContainer) LookupNetIP(
- ctx context.Context,
- network string,
- hostname string,
-) (addrs []netip.Addr, err error) {
- // TODO(e.burkov): Think of extracting this logic to a golibs function if
- // needed anywhere else.
- var isDesiredProto func(ip netip.Addr) (ok bool)
- switch network {
- case "ip4":
- isDesiredProto = (netip.Addr).Is4
- case "ip6":
- isDesiredProto = (netip.Addr).Is6
- case "ip":
- isDesiredProto = func(ip netip.Addr) (ok bool) { return true }
- default:
- return nil, fmt.Errorf("unsupported network: %q", network)
- }
-
- idx := hc.current.Load()
- recs := idx.names[strings.ToLower(hostname)]
-
- addrs = make([]netip.Addr, 0, len(recs))
- for _, rec := range recs {
- if isDesiredProto(rec.Addr) {
- addrs = append(addrs, rec.Addr)
- }
- }
-
- return slices.Clip(addrs), nil
-}
diff --git a/internal/aghnet/hostscontainer_linux.go b/internal/aghnet/hostscontainer_linux.go
deleted file mode 100644
index 290291e9..00000000
--- a/internal/aghnet/hostscontainer_linux.go
+++ /dev/null
@@ -1,17 +0,0 @@
-//go:build linux
-
-package aghnet
-
-import (
- "github.com/AdguardTeam/AdGuardHome/internal/aghos"
-)
-
-func defaultHostsPaths() (paths []string) {
- paths = []string{"etc/hosts"}
-
- if aghos.IsOpenWrt() {
- paths = append(paths, "tmp/hosts")
- }
-
- return paths
-}
diff --git a/internal/aghnet/hostscontainer_others.go b/internal/aghnet/hostscontainer_others.go
deleted file mode 100644
index 61487dc4..00000000
--- a/internal/aghnet/hostscontainer_others.go
+++ /dev/null
@@ -1,7 +0,0 @@
-//go:build !(windows || linux)
-
-package aghnet
-
-func defaultHostsPaths() (paths []string) {
- return []string{"etc/hosts"}
-}
diff --git a/internal/aghnet/hostscontainer_test.go b/internal/aghnet/hostscontainer_test.go
index ac30777f..813b369d 100644
--- a/internal/aghnet/hostscontainer_test.go
+++ b/internal/aghnet/hostscontainer_test.go
@@ -3,13 +3,11 @@ package aghnet_test
import (
"net/netip"
"path"
- "path/filepath"
"sync/atomic"
"testing"
"testing/fstest"
"time"
- "github.com/AdguardTeam/AdGuardHome/internal/aghchan"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/errors"
@@ -20,139 +18,6 @@ import (
"github.com/stretchr/testify/require"
)
-// nl is a newline character.
-const nl = "\n"
-
-// Variables mirroring the etc_hosts file from testdata.
-var (
- addr1000 = netip.MustParseAddr("1.0.0.0")
- addr1001 = netip.MustParseAddr("1.0.0.1")
- addr1002 = netip.MustParseAddr("1.0.0.2")
- addr1003 = netip.MustParseAddr("1.0.0.3")
- addr1004 = netip.MustParseAddr("1.0.0.4")
- addr1357 = netip.MustParseAddr("1.3.5.7")
- addr4216 = netip.MustParseAddr("4.2.1.6")
- addr7531 = netip.MustParseAddr("7.5.3.1")
-
- addr0 = netip.MustParseAddr("::")
- addr1 = netip.MustParseAddr("::1")
- addr2 = netip.MustParseAddr("::2")
- addr3 = netip.MustParseAddr("::3")
- addr4 = netip.MustParseAddr("::4")
- addr42 = netip.MustParseAddr("::42")
- addr13 = netip.MustParseAddr("::13")
- addr31 = netip.MustParseAddr("::31")
-
- hostsSrc = "./" + filepath.Join("./testdata", "etc_hosts")
-
- testHosts = map[netip.Addr][]*hostsfile.Record{
- addr1000: {{
- Addr: addr1000,
- Source: hostsSrc,
- Names: []string{"hello", "hello.world"},
- }, {
- Addr: addr1000,
- Source: hostsSrc,
- Names: []string{"hello.world.again"},
- }, {
- Addr: addr1000,
- Source: hostsSrc,
- Names: []string{"hello.world"},
- }},
- addr1001: {{
- Addr: addr1001,
- Source: hostsSrc,
- Names: []string{"simplehost"},
- }, {
- Addr: addr1001,
- Source: hostsSrc,
- Names: []string{"simplehost"},
- }},
- addr1002: {{
- Addr: addr1002,
- Source: hostsSrc,
- Names: []string{"a.whole", "lot.of", "aliases", "for.testing"},
- }},
- addr1003: {{
- Addr: addr1003,
- Source: hostsSrc,
- Names: []string{"*"},
- }},
- addr1004: {{
- Addr: addr1004,
- Source: hostsSrc,
- Names: []string{"*.com"},
- }},
- addr1357: {{
- Addr: addr1357,
- Source: hostsSrc,
- Names: []string{"domain4", "domain4.alias"},
- }},
- addr7531: {{
- Addr: addr7531,
- Source: hostsSrc,
- Names: []string{"domain4.alias", "domain4"},
- }},
- addr4216: {{
- Addr: addr4216,
- Source: hostsSrc,
- Names: []string{"domain", "domain.alias"},
- }},
- addr0: {{
- Addr: addr0,
- Source: hostsSrc,
- Names: []string{"hello", "hello.world"},
- }, {
- Addr: addr0,
- Source: hostsSrc,
- Names: []string{"hello.world.again"},
- }, {
- Addr: addr0,
- Source: hostsSrc,
- Names: []string{"hello.world"},
- }},
- addr1: {{
- Addr: addr1,
- Source: hostsSrc,
- Names: []string{"simplehost"},
- }, {
- Addr: addr1,
- Source: hostsSrc,
- Names: []string{"simplehost"},
- }},
- addr2: {{
- Addr: addr2,
- Source: hostsSrc,
- Names: []string{"a.whole", "lot.of", "aliases", "for.testing"},
- }},
- addr3: {{
- Addr: addr3,
- Source: hostsSrc,
- Names: []string{"*"},
- }},
- addr4: {{
- Addr: addr4,
- Source: hostsSrc,
- Names: []string{"*.com"},
- }},
- addr42: {{
- Addr: addr42,
- Source: hostsSrc,
- Names: []string{"domain.alias", "domain"},
- }},
- addr13: {{
- Addr: addr13,
- Source: hostsSrc,
- Names: []string{"domain6", "domain6.alias"},
- }},
- addr31: {{
- Addr: addr31,
- Source: hostsSrc,
- Names: []string{"domain6.alias", "domain6"},
- }},
- }
-)
-
func TestNewHostsContainer(t *testing.T) {
const dirname = "dir"
const filename = "file1"
@@ -267,7 +132,21 @@ func TestHostsContainer_refresh(t *testing.T) {
anotherIPStr := "1.2.3.4"
anotherIP := netip.MustParseAddr(anotherIPStr)
- testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}}
+ r1 := &hostsfile.Record{
+ Addr: ip,
+ Source: "file1",
+ Names: []string{"hostname"},
+ }
+ r2 := &hostsfile.Record{
+ Addr: anotherIP,
+ Source: "file2",
+ Names: []string{"alias"},
+ }
+
+ r1Data, _ := r1.MarshalText()
+ r2Data, _ := r2.MarshalText()
+
+ testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: r1Data}}
// event is a convenient alias for an empty struct{} to emit test events.
type event = struct{}
@@ -289,172 +168,47 @@ func TestHostsContainer_refresh(t *testing.T) {
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, hc.Close)
- checkRefresh := func(t *testing.T, want aghnet.Hosts) {
- t.Helper()
-
- upd, ok := aghchan.MustReceive(hc.Upd(), 1*time.Second)
- require.True(t, ok)
-
- assert.Equal(t, want, upd)
- }
+ strg, _ := hostsfile.NewDefaultStorage()
+ strg.Add(r1)
t.Run("initial_refresh", func(t *testing.T) {
- checkRefresh(t, aghnet.Hosts{
- ip: {{
- Addr: ip,
- Source: "file1",
- Names: []string{"hostname"},
- }},
- })
+ upd, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
+ require.True(t, ok)
+
+ assert.True(t, strg.Equal(upd))
})
+ strg.Add(r2)
+
t.Run("second_refresh", func(t *testing.T) {
- testFS["dir/file2"] = &fstest.MapFile{Data: []byte(anotherIPStr + ` alias` + nl)}
+ testFS["dir/file2"] = &fstest.MapFile{Data: r2Data}
eventsCh <- event{}
- checkRefresh(t, aghnet.Hosts{
- ip: {{
- Addr: ip,
- Source: "file1",
- Names: []string{"hostname"},
- }},
- anotherIP: {{
- Addr: anotherIP,
- Source: "file2",
- Names: []string{"alias"},
- }},
- })
+ upd, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
+ require.True(t, ok)
+
+ assert.True(t, strg.Equal(upd))
})
t.Run("double_refresh", func(t *testing.T) {
// Make a change once.
- testFS["dir/file1"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)}
+ testFS["dir/file1"] = &fstest.MapFile{Data: []byte(ipStr + " alias\n")}
eventsCh <- event{}
// Require the changes are written.
- require.Eventually(t, func() bool {
- ips := hc.MatchName("hostname")
+ current, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
+ require.True(t, ok)
- return len(ips) == 0
- }, 5*time.Second, time.Second/2)
+ require.Empty(t, current.ByName("hostname"))
// Make a change again.
- testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}
+ testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + " hostname\n")}
eventsCh <- event{}
// Require the changes are written.
- require.Eventually(t, func() bool {
- ips := hc.MatchName("hostname")
+ current, ok = testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
+ require.True(t, ok)
- return len(ips) > 0
- }, 5*time.Second, time.Second/2)
-
- assert.Len(t, hc.Upd(), 1)
+ require.NotEmpty(t, current.ByName("hostname"))
})
}
-
-func TestHostsContainer_MatchName(t *testing.T) {
- require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
-
- stubWatcher := aghtest.FSWatcher{
- OnEvents: func() (e <-chan struct{}) { return nil },
- OnAdd: func(name string) (err error) { return nil },
- OnClose: func() (err error) { return nil },
- }
-
- testCases := []struct {
- req string
- name string
- want []*hostsfile.Record
- }{{
- req: "simplehost",
- name: "simple",
- want: append(testHosts[addr1001], testHosts[addr1]...),
- }, {
- req: "hello.world",
- name: "hello_alias",
- want: []*hostsfile.Record{
- testHosts[addr1000][0],
- testHosts[addr1000][2],
- testHosts[addr0][0],
- testHosts[addr0][2],
- },
- }, {
- req: "hello.world.again",
- name: "other_line_alias",
- want: []*hostsfile.Record{
- testHosts[addr1000][1],
- testHosts[addr0][1],
- },
- }, {
- req: "say.hello",
- name: "hello_subdomain",
- want: nil,
- }, {
- req: "say.hello.world",
- name: "hello_alias_subdomain",
- want: nil,
- }, {
- req: "for.testing",
- name: "lots_of_aliases",
- want: append(testHosts[addr1002], testHosts[addr2]...),
- }, {
- req: "nonexistent.example",
- name: "non-existing",
- want: nil,
- }, {
- req: "domain",
- name: "issue_4216_4_6",
- want: append(testHosts[addr4216], testHosts[addr42]...),
- }, {
- req: "domain4",
- name: "issue_4216_4",
- want: append(testHosts[addr1357], testHosts[addr7531]...),
- }, {
- req: "domain6",
- name: "issue_4216_6",
- want: append(testHosts[addr13], testHosts[addr31]...),
- }}
-
- hc, err := aghnet.NewHostsContainer(testdata, &stubWatcher, "etc_hosts")
- require.NoError(t, err)
- testutil.CleanupAndRequireSuccess(t, hc.Close)
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- recs := hc.MatchName(tc.req)
- assert.Equal(t, tc.want, recs)
- })
- }
-}
-
-func TestHostsContainer_MatchAddr(t *testing.T) {
- require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
-
- stubWatcher := aghtest.FSWatcher{
- OnEvents: func() (e <-chan struct{}) { return nil },
- OnAdd: func(name string) (err error) { return nil },
- OnClose: func() (err error) { return nil },
- }
-
- hc, err := aghnet.NewHostsContainer(testdata, &stubWatcher, "etc_hosts")
- require.NoError(t, err)
- testutil.CleanupAndRequireSuccess(t, hc.Close)
-
- testCases := []struct {
- req netip.Addr
- name string
- want []*hostsfile.Record
- }{{
- req: netip.AddrFrom4([4]byte{1, 0, 0, 1}),
- name: "reverse",
- want: testHosts[addr1001],
- }}
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- recs := hc.MatchAddr(tc.req)
- assert.Equal(t, tc.want, recs)
- })
- }
-}
diff --git a/internal/aghnet/hostscontainer_windows.go b/internal/aghnet/hostscontainer_windows.go
deleted file mode 100644
index 7bbf7ac0..00000000
--- a/internal/aghnet/hostscontainer_windows.go
+++ /dev/null
@@ -1,32 +0,0 @@
-//go:build windows
-
-package aghnet
-
-import (
- "os"
- "path"
- "path/filepath"
- "strings"
-
- "github.com/AdguardTeam/golibs/log"
- "golang.org/x/sys/windows"
-)
-
-func defaultHostsPaths() (paths []string) {
- sysDir, err := windows.GetSystemDirectory()
- if err != nil {
- log.Error("aghnet: getting system directory: %s", err)
-
- return []string{}
- }
-
- // Split all the elements of the path to join them afterwards. This is
- // needed to make the Windows-specific path string returned by
- // windows.GetSystemDirectory to be compatible with fs.FS.
- pathElems := strings.Split(sysDir, string(os.PathSeparator))
- if len(pathElems) > 0 && pathElems[0] == filepath.VolumeName(sysDir) {
- pathElems = pathElems[1:]
- }
-
- return []string{path.Join(append(pathElems, "drivers/etc/hosts")...)}
-}
diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go
index 2a77803b..06f65840 100644
--- a/internal/aghnet/net_test.go
+++ b/internal/aghnet/net_test.go
@@ -1,11 +1,9 @@
package aghnet_test
import (
- "io/fs"
"net"
"net/netip"
"net/url"
- "os"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
@@ -18,9 +16,6 @@ func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
-// testdata is the filesystem containing data for testing the package.
-var testdata fs.FS = os.DirFS("./testdata")
-
func TestParseAddrPort(t *testing.T) {
const defaultPort = 1
diff --git a/internal/aghnet/testdata/etc_hosts b/internal/aghnet/testdata/etc_hosts
deleted file mode 100644
index 7cd0c770..00000000
--- a/internal/aghnet/testdata/etc_hosts
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# Test /etc/hosts file
-#
-
-1.0.0.1 simplehost
-1.0.0.0 hello hello.world
-
-# See https://github.com/AdguardTeam/AdGuardHome/issues/3846.
-1.0.0.2 a.whole lot.of aliases for.testing
-
-# See https://github.com/AdguardTeam/AdGuardHome/issues/3946.
-1.0.0.3 *
-1.0.0.4 *.com
-
-# See https://github.com/AdguardTeam/AdGuardHome/issues/4079.
-1.0.0.0 hello.world.again
-
-# Duplicates of a main host and an alias.
-1.0.0.1 simplehost
-1.0.0.0 hello.world
-
-# Same for IPv6.
-::1 simplehost
-:: hello hello.world
-::2 a.whole lot.of aliases for.testing
-::3 *
-::4 *.com
-:: hello.world.again
-::1 simplehost
-:: hello.world
-
-# See https://github.com/AdguardTeam/AdGuardHome/issues/4216.
-4.2.1.6 domain domain.alias
-::42 domain.alias domain
-1.3.5.7 domain4 domain4.alias
-7.5.3.1 domain4.alias domain4
-::13 domain6 domain6.alias
-::31 domain6.alias domain6
diff --git a/internal/aghnet/testdata/ifaces b/internal/aghnet/testdata/ifaces
deleted file mode 100644
index b98b0409..00000000
--- a/internal/aghnet/testdata/ifaces
+++ /dev/null
@@ -1 +0,0 @@
-iface sample_name inet static
diff --git a/internal/aghnet/testdata/include-subsources b/internal/aghnet/testdata/include-subsources
deleted file mode 100644
index 5391a5b3..00000000
--- a/internal/aghnet/testdata/include-subsources
+++ /dev/null
@@ -1,5 +0,0 @@
-# The "testdata" part is added here because the test is actually run from the
-# parent directory. Real interface files usually contain only absolute paths.
-
-source ./testdata/ifaces
-source ./testdata/*
diff --git a/internal/client/addrproc.go b/internal/client/addrproc.go
index 2b8046f6..76ff1367 100644
--- a/internal/client/addrproc.go
+++ b/internal/client/addrproc.go
@@ -262,8 +262,7 @@ func (p *DefaultAddrProc) processRDNS(ip netip.Addr) (host string) {
// shouldResolve returns false if ip is a loopback address, or ip is private and
// resolving of private addresses is disabled.
func (p *DefaultAddrProc) shouldResolve(ip netip.Addr) (ok bool) {
- return !ip.IsLoopback() &&
- (p.usePrivateRDNS || !p.privateSubnets.Contains(ip.AsSlice()))
+ return !ip.IsLoopback() && (p.usePrivateRDNS || !p.privateSubnets.Contains(ip))
}
// processWHOIS looks up the information about clients' IP addresses in the
diff --git a/internal/client/client.go b/internal/client/client.go
index 6b81b9b8..d0a75045 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -7,6 +7,8 @@ package client
import (
"encoding"
"fmt"
+
+ "github.com/AdguardTeam/AdGuardHome/internal/whois"
)
// Source represents the source from which the information about the client has
@@ -15,8 +17,7 @@ type Source uint8
// Clients information sources. The order determines the priority.
const (
- SourceNone Source = iota
- SourceWHOIS
+ SourceWHOIS Source = iota + 1
SourceARP
SourceRDNS
SourceDHCP
@@ -52,3 +53,107 @@ var _ encoding.TextMarshaler = Source(0)
func (cs Source) MarshalText() (text []byte, err error) {
return []byte(cs.String()), nil
}
+
+// Runtime is a client information from different sources.
+type Runtime struct {
+ // whois is the filtered WHOIS information of a client.
+ whois *whois.Info
+
+ // arp is the ARP information of a client. nil indicates that there is no
+ // information from the source. Empty non-nil slice indicates that the data
+ // from the source is present, but empty.
+ arp []string
+
+ // rdns is the RDNS information of a client. nil indicates that there is no
+ // information from the source. Empty non-nil slice indicates that the data
+ // from the source is present, but empty.
+ rdns []string
+
+ // dhcp is the DHCP information of a client. nil indicates that there is no
+ // information from the source. Empty non-nil slice indicates that the data
+ // from the source is present, but empty.
+ dhcp []string
+
+ // hostsFile is the information from the hosts file. nil indicates that
+ // there is no information from the source. Empty non-nil slice indicates
+ // that the data from the source is present, but empty.
+ hostsFile []string
+}
+
+// Info returns a client information from the highest-priority source.
+func (r *Runtime) Info() (cs Source, host string) {
+ info := []string{}
+
+ switch {
+ case r.hostsFile != nil:
+ cs, info = SourceHostsFile, r.hostsFile
+ case r.dhcp != nil:
+ cs, info = SourceDHCP, r.dhcp
+ case r.rdns != nil:
+ cs, info = SourceRDNS, r.rdns
+ case r.arp != nil:
+ cs, info = SourceARP, r.arp
+ case r.whois != nil:
+ cs = SourceWHOIS
+ }
+
+ if len(info) == 0 {
+ return cs, ""
+ }
+
+ // TODO(s.chzhen): Return the full information.
+ return cs, info[0]
+}
+
+// SetInfo sets a host as a client information from the cs.
+func (r *Runtime) SetInfo(cs Source, hosts []string) {
+ if len(hosts) == 1 && hosts[0] == "" {
+ hosts = []string{}
+ }
+
+ switch cs {
+ case SourceARP:
+ r.arp = hosts
+ case SourceRDNS:
+ r.rdns = hosts
+ case SourceDHCP:
+ r.dhcp = hosts
+ case SourceHostsFile:
+ r.hostsFile = hosts
+ }
+}
+
+// WHOIS returns a WHOIS client information.
+func (r *Runtime) WHOIS() (info *whois.Info) {
+ return r.whois
+}
+
+// SetWHOIS sets a WHOIS client information. info must be non-nil.
+func (r *Runtime) SetWHOIS(info *whois.Info) {
+ r.whois = info
+}
+
+// Unset clears a cs information.
+func (r *Runtime) Unset(cs Source) {
+ switch cs {
+ case SourceWHOIS:
+ r.whois = nil
+ case SourceARP:
+ r.arp = nil
+ case SourceRDNS:
+ r.rdns = nil
+ case SourceDHCP:
+ r.dhcp = nil
+ case SourceHostsFile:
+ r.hostsFile = nil
+ }
+}
+
+// IsEmpty returns true if there is no information from any source.
+func (r *Runtime) IsEmpty() (ok bool) {
+ return r.whois == nil &&
+ r.arp == nil &&
+ r.rdns == nil &&
+ r.dhcp == nil &&
+ r.hostsFile == nil
+}
diff --git a/internal/configmigrate/configmigrate.go b/internal/configmigrate/configmigrate.go
new file mode 100644
index 00000000..6e8845e0
--- /dev/null
+++ b/internal/configmigrate/configmigrate.go
@@ -0,0 +1,5 @@
+// Package configmigrate provides a way to upgrade the YAML configuration file.
+package configmigrate
+
+// LastSchemaVersion is the most recent schema version.
+const LastSchemaVersion uint = 28
diff --git a/internal/confmigrate/migrations_internal_test.go b/internal/configmigrate/migrations_internal_test.go
similarity index 95%
rename from internal/confmigrate/migrations_internal_test.go
rename to internal/configmigrate/migrations_internal_test.go
index 513646e8..5349102f 100644
--- a/internal/confmigrate/migrations_internal_test.go
+++ b/internal/configmigrate/migrations_internal_test.go
@@ -1,9 +1,10 @@
-package confmigrate
+package configmigrate
import (
"testing"
"time"
+ "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/timeutil"
@@ -1646,3 +1647,84 @@ func TestUpgradeSchema26to27(t *testing.T) {
})
}
}
+
+func TestUpgradeSchema27to28(t *testing.T) {
+ const newSchemaVer = 28
+
+ testCases := []struct {
+ in yobj
+ want yobj
+ name string
+ }{{
+ name: "empty",
+ in: yobj{},
+ want: yobj{
+ "schema_version": newSchemaVer,
+ },
+ }, {
+ name: "load_balance",
+ in: yobj{
+ "dns": yobj{
+ "all_servers": false,
+ "fastest_addr": false,
+ },
+ },
+ want: yobj{
+ "dns": yobj{
+ "upstream_mode": dnsforward.UpstreamModeLoadBalance,
+ },
+ "schema_version": newSchemaVer,
+ },
+ }, {
+ name: "parallel",
+ in: yobj{
+ "dns": yobj{
+ "all_servers": true,
+ "fastest_addr": false,
+ },
+ },
+ want: yobj{
+ "dns": yobj{
+ "upstream_mode": dnsforward.UpstreamModeParallel,
+ },
+ "schema_version": newSchemaVer,
+ },
+ }, {
+ name: "parallel_fastest",
+ in: yobj{
+ "dns": yobj{
+ "all_servers": true,
+ "fastest_addr": true,
+ },
+ },
+ want: yobj{
+ "dns": yobj{
+ "upstream_mode": dnsforward.UpstreamModeParallel,
+ },
+ "schema_version": newSchemaVer,
+ },
+ }, {
+ name: "load_balance",
+ in: yobj{
+ "dns": yobj{
+ "all_servers": false,
+ "fastest_addr": true,
+ },
+ },
+ want: yobj{
+ "dns": yobj{
+ "upstream_mode": dnsforward.UpstreamModeFastestAddr,
+ },
+ "schema_version": newSchemaVer,
+ },
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ err := migrateTo28(tc.in)
+ require.NoError(t, err)
+
+ assert.Equal(t, tc.want, tc.in)
+ })
+ }
+}
diff --git a/internal/confmigrate/migrator.go b/internal/configmigrate/migrator.go
similarity index 95%
rename from internal/confmigrate/migrator.go
rename to internal/configmigrate/migrator.go
index 636db690..ebdf6ba7 100644
--- a/internal/confmigrate/migrator.go
+++ b/internal/configmigrate/migrator.go
@@ -1,5 +1,4 @@
-// Package confmigrate provides a way to upgrade the YAML configuration file.
-package confmigrate
+package configmigrate
import (
"bytes"
@@ -9,9 +8,6 @@ import (
yaml "gopkg.in/yaml.v3"
)
-// LastSchemaVersion is the most recent schema version.
-const LastSchemaVersion uint = 27
-
// Config is a the configuration for initializing a [Migrator].
type Config struct {
// WorkingDir is an absolute path to the working directory of AdGuardHome.
@@ -123,6 +119,7 @@ func (m *Migrator) upgradeConfigSchema(current, target uint, diskConf yobj) (err
24: migrateTo25,
25: migrateTo26,
26: migrateTo27,
+ 27: migrateTo28,
}
for i, migrate := range upgrades[current:target] {
diff --git a/internal/confmigrate/migrator_test.go b/internal/configmigrate/migrator_test.go
similarity index 97%
rename from internal/confmigrate/migrator_test.go
rename to internal/configmigrate/migrator_test.go
index de3aa401..442713a4 100644
--- a/internal/confmigrate/migrator_test.go
+++ b/internal/configmigrate/migrator_test.go
@@ -1,4 +1,4 @@
-package confmigrate_test
+package configmigrate_test
import (
"io/fs"
@@ -6,7 +6,7 @@ import (
"path"
"testing"
- "github.com/AdguardTeam/AdGuardHome/internal/confmigrate"
+ "github.com/AdguardTeam/AdGuardHome/internal/configmigrate"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
@@ -200,7 +200,7 @@ func TestMigrateConfig_Migrate(t *testing.T) {
wantBody, err := fs.ReadFile(testdata, path.Join(t.Name(), outputFileName))
require.NoError(t, err)
- migrator := confmigrate.New(&confmigrate.Config{
+ migrator := configmigrate.New(&configmigrate.Config{
WorkingDir: t.Name(),
})
newBody, upgraded, err := migrator.Migrate(body, tc.targetVersion)
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v1/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v1/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v1/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v1/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v10/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v10/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v10/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v10/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v11/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v11/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v11/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v11/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v12/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v12/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v12/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v12/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v13/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v13/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v13/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v13/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v14/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v14/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v14/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v14/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v15/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v15/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v15/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v15/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v16/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v16/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v16/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v16/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v17/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v17/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v17/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v17/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v18/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v18/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v18/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v18/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v19/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v19/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v19/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v19/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v2/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v2/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v2/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v2/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v20/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v20/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v20/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v20/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v21/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v21/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v21/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v21/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v22/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v22/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v22/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v22/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v23/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v23/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v23/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v23/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v24/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v24/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v24/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v24/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v25/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v25/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v25/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v25/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v26/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v26/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v26/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v26/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v27/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v27/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v27/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v27/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v3/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v3/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v3/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v3/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v4/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v4/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v4/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v4/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v5/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v5/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v5/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v5/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v6/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v6/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v6/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v6/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v7/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v7/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v7/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v7/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v8/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v8/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v8/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v8/output.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/input.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v9/input.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/input.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v9/input.yml
diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/output.yml b/internal/configmigrate/testdata/TestMigrateConfig_Migrate/v9/output.yml
similarity index 100%
rename from internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/output.yml
rename to internal/configmigrate/testdata/TestMigrateConfig_Migrate/v9/output.yml
diff --git a/internal/confmigrate/v1.go b/internal/configmigrate/v1.go
similarity index 97%
rename from internal/confmigrate/v1.go
rename to internal/configmigrate/v1.go
index 780c0273..550ac9c8 100644
--- a/internal/confmigrate/v1.go
+++ b/internal/configmigrate/v1.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import (
"os"
diff --git a/internal/confmigrate/v10.go b/internal/configmigrate/v10.go
similarity index 99%
rename from internal/confmigrate/v10.go
rename to internal/configmigrate/v10.go
index 0cf10c2f..f987b6b5 100644
--- a/internal/confmigrate/v10.go
+++ b/internal/configmigrate/v10.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import (
"fmt"
diff --git a/internal/confmigrate/v11.go b/internal/configmigrate/v11.go
similarity index 96%
rename from internal/confmigrate/v11.go
rename to internal/configmigrate/v11.go
index d93fbcd8..c2ba1516 100644
--- a/internal/confmigrate/v11.go
+++ b/internal/configmigrate/v11.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo11 performs the following changes:
//
diff --git a/internal/confmigrate/v12.go b/internal/configmigrate/v12.go
similarity index 97%
rename from internal/confmigrate/v12.go
rename to internal/configmigrate/v12.go
index 6703561a..40afca80 100644
--- a/internal/confmigrate/v12.go
+++ b/internal/configmigrate/v12.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import (
"time"
diff --git a/internal/confmigrate/v13.go b/internal/configmigrate/v13.go
similarity index 96%
rename from internal/confmigrate/v13.go
rename to internal/configmigrate/v13.go
index a69592bf..3a4034d1 100644
--- a/internal/confmigrate/v13.go
+++ b/internal/configmigrate/v13.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo13 performs the following changes:
//
diff --git a/internal/confmigrate/v14.go b/internal/configmigrate/v14.go
similarity index 98%
rename from internal/confmigrate/v14.go
rename to internal/configmigrate/v14.go
index d259a2a6..49e54ca3 100644
--- a/internal/confmigrate/v14.go
+++ b/internal/configmigrate/v14.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo14 performs the following changes:
//
diff --git a/internal/confmigrate/v15.go b/internal/configmigrate/v15.go
similarity index 98%
rename from internal/confmigrate/v15.go
rename to internal/configmigrate/v15.go
index 904ef110..85f6d14b 100644
--- a/internal/confmigrate/v15.go
+++ b/internal/configmigrate/v15.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo15 performs the following changes:
//
diff --git a/internal/confmigrate/v16.go b/internal/configmigrate/v16.go
similarity index 98%
rename from internal/confmigrate/v16.go
rename to internal/configmigrate/v16.go
index b174d4cb..d9ed1ca2 100644
--- a/internal/confmigrate/v16.go
+++ b/internal/configmigrate/v16.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo16 performs the following changes:
//
diff --git a/internal/confmigrate/v17.go b/internal/configmigrate/v17.go
similarity index 96%
rename from internal/confmigrate/v17.go
rename to internal/configmigrate/v17.go
index a3a04460..5672d14c 100644
--- a/internal/confmigrate/v17.go
+++ b/internal/configmigrate/v17.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo17 performs the following changes:
//
diff --git a/internal/confmigrate/v18.go b/internal/configmigrate/v18.go
similarity index 97%
rename from internal/confmigrate/v18.go
rename to internal/configmigrate/v18.go
index 28c55723..7fc5c639 100644
--- a/internal/confmigrate/v18.go
+++ b/internal/configmigrate/v18.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo18 performs the following changes:
//
diff --git a/internal/confmigrate/v19.go b/internal/configmigrate/v19.go
similarity index 98%
rename from internal/confmigrate/v19.go
rename to internal/configmigrate/v19.go
index 6fbb6d4f..229be5dc 100644
--- a/internal/confmigrate/v19.go
+++ b/internal/configmigrate/v19.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import "github.com/AdguardTeam/golibs/log"
diff --git a/internal/confmigrate/v2.go b/internal/configmigrate/v2.go
similarity index 97%
rename from internal/confmigrate/v2.go
rename to internal/configmigrate/v2.go
index 1faff87f..d4652d10 100644
--- a/internal/confmigrate/v2.go
+++ b/internal/configmigrate/v2.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import (
"os"
diff --git a/internal/confmigrate/v20.go b/internal/configmigrate/v20.go
similarity index 96%
rename from internal/confmigrate/v20.go
rename to internal/configmigrate/v20.go
index e86c36c7..d207ac0b 100644
--- a/internal/confmigrate/v20.go
+++ b/internal/configmigrate/v20.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import (
"time"
diff --git a/internal/confmigrate/v21.go b/internal/configmigrate/v21.go
similarity index 97%
rename from internal/confmigrate/v21.go
rename to internal/configmigrate/v21.go
index afda11bf..362f2ff1 100644
--- a/internal/confmigrate/v21.go
+++ b/internal/configmigrate/v21.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo21 performs the following changes:
//
diff --git a/internal/confmigrate/v22.go b/internal/configmigrate/v22.go
similarity index 98%
rename from internal/confmigrate/v22.go
rename to internal/configmigrate/v22.go
index 4d5de732..6a0b24c0 100644
--- a/internal/confmigrate/v22.go
+++ b/internal/configmigrate/v22.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import (
"fmt"
diff --git a/internal/confmigrate/v23.go b/internal/configmigrate/v23.go
similarity index 98%
rename from internal/confmigrate/v23.go
rename to internal/configmigrate/v23.go
index 6cbcbcf5..5cda964d 100644
--- a/internal/confmigrate/v23.go
+++ b/internal/configmigrate/v23.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import (
"fmt"
diff --git a/internal/confmigrate/v24.go b/internal/configmigrate/v24.go
similarity index 98%
rename from internal/confmigrate/v24.go
rename to internal/configmigrate/v24.go
index e8df5718..f9d781e5 100644
--- a/internal/confmigrate/v24.go
+++ b/internal/configmigrate/v24.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo24 performs the following changes:
//
diff --git a/internal/confmigrate/v25.go b/internal/configmigrate/v25.go
similarity index 96%
rename from internal/confmigrate/v25.go
rename to internal/configmigrate/v25.go
index e574610e..a07dec70 100644
--- a/internal/confmigrate/v25.go
+++ b/internal/configmigrate/v25.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo25 performs the following changes:
//
diff --git a/internal/confmigrate/v26.go b/internal/configmigrate/v26.go
similarity index 99%
rename from internal/confmigrate/v26.go
rename to internal/configmigrate/v26.go
index 5ead91df..a19b9038 100644
--- a/internal/confmigrate/v26.go
+++ b/internal/configmigrate/v26.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo26 performs the following changes:
//
diff --git a/internal/confmigrate/v27.go b/internal/configmigrate/v27.go
similarity index 98%
rename from internal/confmigrate/v27.go
rename to internal/configmigrate/v27.go
index ddba8671..0beba4db 100644
--- a/internal/confmigrate/v27.go
+++ b/internal/configmigrate/v27.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo27 performs the following changes:
//
diff --git a/internal/configmigrate/v28.go b/internal/configmigrate/v28.go
new file mode 100644
index 00000000..c32f9f3f
--- /dev/null
+++ b/internal/configmigrate/v28.go
@@ -0,0 +1,47 @@
+package configmigrate
+
+import (
+ "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
+)
+
+// migrateTo28 performs the following changes:
+//
+// # BEFORE:
+// 'dns':
+// 'all_servers': true
+// 'fastest_addr': true
+// # …
+// # …
+//
+// # AFTER:
+// 'dns':
+// 'upstream_mode': 'parallel'
+// # …
+// # …
+func migrateTo28(diskConf yobj) (err error) {
+ diskConf["schema_version"] = 28
+
+ dns, ok, err := fieldVal[yobj](diskConf, "dns")
+ if !ok {
+ return err
+ }
+
+ allServers, _, _ := fieldVal[bool](dns, "all_servers")
+ fastestAddr, _, _ := fieldVal[bool](dns, "fastest_addr")
+
+ var upstreamModeType dnsforward.UpstreamMode
+ if allServers {
+ upstreamModeType = dnsforward.UpstreamModeParallel
+ } else if fastestAddr {
+ upstreamModeType = dnsforward.UpstreamModeFastestAddr
+ } else {
+ upstreamModeType = dnsforward.UpstreamModeLoadBalance
+ }
+
+ dns["upstream_mode"] = upstreamModeType
+
+ delete(dns, "all_servers")
+ delete(dns, "fastest_addr")
+
+ return nil
+}
diff --git a/internal/confmigrate/v3.go b/internal/configmigrate/v3.go
similarity index 96%
rename from internal/confmigrate/v3.go
rename to internal/configmigrate/v3.go
index 8220c5df..a62dd87d 100644
--- a/internal/confmigrate/v3.go
+++ b/internal/configmigrate/v3.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo3 performs the following changes:
//
diff --git a/internal/confmigrate/v4.go b/internal/configmigrate/v4.go
similarity index 95%
rename from internal/confmigrate/v4.go
rename to internal/configmigrate/v4.go
index 467cc05c..c4d40fb7 100644
--- a/internal/confmigrate/v4.go
+++ b/internal/configmigrate/v4.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo4 performs the following changes:
//
diff --git a/internal/confmigrate/v5.go b/internal/configmigrate/v5.go
similarity index 97%
rename from internal/confmigrate/v5.go
rename to internal/configmigrate/v5.go
index 7bdd4d00..e167a8e0 100644
--- a/internal/confmigrate/v5.go
+++ b/internal/configmigrate/v5.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import (
"fmt"
diff --git a/internal/confmigrate/v6.go b/internal/configmigrate/v6.go
similarity index 97%
rename from internal/confmigrate/v6.go
rename to internal/configmigrate/v6.go
index aa741ba9..733785a0 100644
--- a/internal/confmigrate/v6.go
+++ b/internal/configmigrate/v6.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import "fmt"
diff --git a/internal/confmigrate/v7.go b/internal/configmigrate/v7.go
similarity index 98%
rename from internal/confmigrate/v7.go
rename to internal/configmigrate/v7.go
index 1db348bd..61ee1e26 100644
--- a/internal/confmigrate/v7.go
+++ b/internal/configmigrate/v7.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo7 performs the following changes:
//
diff --git a/internal/confmigrate/v8.go b/internal/configmigrate/v8.go
similarity index 96%
rename from internal/confmigrate/v8.go
rename to internal/configmigrate/v8.go
index f8075dfb..e6283f64 100644
--- a/internal/confmigrate/v8.go
+++ b/internal/configmigrate/v8.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo8 performs the following changes:
//
diff --git a/internal/confmigrate/v9.go b/internal/configmigrate/v9.go
similarity index 95%
rename from internal/confmigrate/v9.go
rename to internal/configmigrate/v9.go
index b8fc9a72..b0583f70 100644
--- a/internal/confmigrate/v9.go
+++ b/internal/configmigrate/v9.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
// migrateTo9 performs the following changes:
//
diff --git a/internal/confmigrate/yaml.go b/internal/configmigrate/yaml.go
similarity index 98%
rename from internal/confmigrate/yaml.go
rename to internal/configmigrate/yaml.go
index 778cd1fd..c2e2ff08 100644
--- a/internal/confmigrate/yaml.go
+++ b/internal/configmigrate/yaml.go
@@ -1,4 +1,4 @@
-package confmigrate
+package configmigrate
import (
"fmt"
diff --git a/internal/dhcpsvc/v4.go b/internal/dhcpsvc/v4.go
index 44c4f840..e74c06b3 100644
--- a/internal/dhcpsvc/v4.go
+++ b/internal/dhcpsvc/v4.go
@@ -6,7 +6,10 @@ import (
"net/netip"
"time"
+ "github.com/AdguardTeam/golibs/log"
+ "github.com/AdguardTeam/golibs/netutil"
"github.com/google/gopacket/layers"
+ "golang.org/x/exp/slices"
)
// IPv4Config is the interface-specific configuration for DHCPv4.
@@ -26,7 +29,9 @@ type IPv4Config struct {
// RangeEnd is the last address in the range to assign to DHCP clients.
RangeEnd netip.Addr
- // Options is the list of DHCP options to send to DHCP clients.
+ // Options is the list of DHCP options to send to DHCP clients. The options
+ // having a zero value within the Length field are treated as deletions of
+ // the corresponding options, either implicit or explicit.
Options layers.DHCPOptions
// LeaseDuration is the TTL of a DHCP lease.
@@ -73,7 +78,14 @@ type iface4 struct {
// name is the name of the interface.
name string
- // TODO(e.burkov): Add options.
+ // implicitOpts are the options listed in Appendix A of RFC 2131 and
+ // initialized with default values. It must not have intersections with
+ // explicitOpts.
+ implicitOpts layers.DHCPOptions
+
+ // explicitOpts are the user-configured options. It must not have
+ // intersections with implicitOpts.
+ explicitOpts layers.DHCPOptions
// leaseTTL is the time-to-live of dynamic leases on this interface.
leaseTTL time.Duration
@@ -103,11 +115,206 @@ func newIface4(name string, conf *IPv4Config) (i *iface4, err error) {
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
}
- return &iface4{
+ i = &iface4{
name: name,
gateway: conf.GatewayIP,
subnet: subnet,
addrSpace: addrSpace,
leaseTTL: conf.LeaseDuration,
- }, nil
+ }
+ i.implicitOpts, i.explicitOpts = conf.options()
+
+ return i, nil
+}
+
+// options returns the implicit and explicit options for the interface. The two
+// lists are disjoint and the implicit options are initialized with default
+// values.
+//
+// TODO(e.burkov): DRY with the IPv6 version.
+func (conf *IPv4Config) options() (implicit, explicit layers.DHCPOptions) {
+ // Set default values of host configuration parameters listed in Appendix A
+ // of RFC-2131.
+ implicit = layers.DHCPOptions{
+ // Values From Configuration
+
+ layers.NewDHCPOption(layers.DHCPOptSubnetMask, conf.SubnetMask.AsSlice()),
+ layers.NewDHCPOption(layers.DHCPOptRouter, conf.GatewayIP.AsSlice()),
+
+ // IP-Layer Per Host
+
+ // An Internet host that includes embedded gateway code MUST have a
+ // configuration switch to disable the gateway function, and this switch
+ // MUST default to the non-gateway mode.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.5.
+ layers.NewDHCPOption(layers.DHCPOptIPForwarding, []byte{0x0}),
+
+ // A host that supports non-local source-routing MUST have a
+ // configurable switch to disable forwarding, and this switch MUST
+ // default to disabled.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.5.
+ layers.NewDHCPOption(layers.DHCPOptSourceRouting, []byte{0x0}),
+
+ // Do not set the Policy Filter Option since it only makes sense when
+ // the non-local source routing is enabled.
+
+ // The minimum legal value is 576.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc2132#section-4.4.
+ layers.NewDHCPOption(layers.DHCPOptDatagramMTU, []byte{0x2, 0x40}),
+
+ // Set the current recommended default time to live for the Internet
+ // Protocol which is 64.
+ //
+ // See https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml#ip-parameters-2.
+ layers.NewDHCPOption(layers.DHCPOptDefaultTTL, []byte{0x40}),
+
+ // For example, after the PTMU estimate is decreased, the timeout should
+ // be set to 10 minutes; once this timer expires and a larger MTU is
+ // attempted, the timeout can be set to a much smaller value.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1191#section-6.6.
+ layers.NewDHCPOption(layers.DHCPOptPathMTUAgingTimeout, []byte{0x0, 0x0, 0x2, 0x58}),
+
+ // There is a table describing the MTU values representing all major
+ // data-link technologies in use in the Internet so that each set of
+ // similar MTUs is associated with a plateau value equal to the lowest
+ // MTU in the group.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1191#section-7.
+ layers.NewDHCPOption(layers.DHCPOptPathPlateuTableOption, []byte{
+ 0x0, 0x44,
+ 0x1, 0x28,
+ 0x1, 0xFC,
+ 0x3, 0xEE,
+ 0x5, 0xD4,
+ 0x7, 0xD2,
+ 0x11, 0x0,
+ 0x1F, 0xE6,
+ 0x45, 0xFA,
+ }),
+
+ // IP-Layer Per Interface
+
+ // Don't set the Interface MTU because client may choose the value on
+ // their own since it's listed in the [Host Requirements RFC]. It also
+ // seems the values listed there sometimes appear obsolete, see
+ // https://github.com/AdguardTeam/AdGuardHome/issues/5281.
+ //
+ // [Host Requirements RFC]: https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.3.
+
+ // Set the All Subnets Are Local Option to false since commonly the
+ // connected hosts aren't expected to be multihomed.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.3.
+ layers.NewDHCPOption(layers.DHCPOptAllSubsLocal, []byte{0x0}),
+
+ // Set the Perform Mask Discovery Option to false to provide the subnet
+ // mask by options only.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.2.9.
+ layers.NewDHCPOption(layers.DHCPOptMaskDiscovery, []byte{0x0}),
+
+ // A system MUST NOT send an Address Mask Reply unless it is an
+ // authoritative agent for address masks. An authoritative agent may be
+ // a host or a gateway, but it MUST be explicitly configured as a
+ // address mask agent.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.2.9.
+ layers.NewDHCPOption(layers.DHCPOptMaskSupplier, []byte{0x0}),
+
+ // Set the Perform Router Discovery Option to true as per Router
+ // Discovery Document.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
+ layers.NewDHCPOption(layers.DHCPOptRouterDiscovery, []byte{0x1}),
+
+ // The all-routers address is preferred wherever possible.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
+ layers.NewDHCPOption(layers.DHCPOptSolicitAddr, netutil.IPv4allrouter()),
+
+ // Don't set the Static Routes Option since it should be set up by
+ // system administrator.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.1.2.
+
+ // A datagram with the destination address of limited broadcast will be
+ // received by every host on the connected physical network but will not
+ // be forwarded outside that network.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.1.3.
+ layers.NewDHCPOption(layers.DHCPOptBroadcastAddr, netutil.IPv4bcast()),
+
+ // Link-Layer Per Interface
+
+ // If the system does not dynamically negotiate use of the trailer
+ // protocol on a per-destination basis, the default configuration MUST
+ // disable the protocol.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-2.3.1.
+ layers.NewDHCPOption(layers.DHCPOptARPTrailers, []byte{0x0}),
+
+ // For proxy ARP situations, the timeout needs to be on the order of a
+ // minute.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-2.3.2.1.
+ layers.NewDHCPOption(layers.DHCPOptARPTimeout, []byte{0x0, 0x0, 0x0, 0x3C}),
+
+ // An Internet host that implements sending both the RFC-894 and the
+ // RFC-1042 encapsulations MUST provide a configuration switch to select
+ // which is sent, and this switch MUST default to RFC-894.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-2.3.3.
+ layers.NewDHCPOption(layers.DHCPOptEthernetEncap, []byte{0x0}),
+
+ // TCP Per Host
+
+ // A fixed value must be at least big enough for the Internet diameter,
+ // i.e., the longest possible path. A reasonable value is about twice
+ // the diameter, to allow for continued Internet growth.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.1.7.
+ layers.NewDHCPOption(layers.DHCPOptTCPTTL, []byte{0x0, 0x0, 0x0, 0x3C}),
+
+ // The interval MUST be configurable and MUST default to no less than
+ // two hours.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-4.2.3.6.
+ layers.NewDHCPOption(layers.DHCPOptTCPKeepAliveInt, []byte{0x0, 0x0, 0x1C, 0x20}),
+
+ // Unfortunately, some misbehaved TCP implementations fail to respond to
+ // a probe segment unless it contains data.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc1122#section-4.2.3.6.
+ layers.NewDHCPOption(layers.DHCPOptTCPKeepAliveGarbage, []byte{0x1}),
+ }
+ slices.SortFunc(implicit, compareV4OptionCodes)
+
+ // Set values for explicitly configured options.
+ for _, exp := range conf.Options {
+ i, found := slices.BinarySearchFunc(implicit, exp, compareV4OptionCodes)
+ if found {
+ implicit = slices.Delete(implicit, i, i+1)
+ }
+
+ i, found = slices.BinarySearchFunc(explicit, exp, compareV4OptionCodes)
+ if exp.Length > 0 {
+ explicit = slices.Insert(explicit, i, exp)
+ } else if found {
+ explicit = slices.Delete(explicit, i, i+1)
+ }
+ }
+
+ log.Debug("dhcpsvc: v4: implicit options: %s", implicit)
+ log.Debug("dhcpsvc: v4: explicit options: %s", explicit)
+
+ return implicit, explicit
+}
+
+// compareV4OptionCodes compares option codes of a and b.
+func compareV4OptionCodes(a, b layers.DHCPOption) (res int) {
+ return int(a.Type) - int(b.Type)
}
diff --git a/internal/dhcpsvc/v4_internal_test.go b/internal/dhcpsvc/v4_internal_test.go
new file mode 100644
index 00000000..0b65366e
--- /dev/null
+++ b/internal/dhcpsvc/v4_internal_test.go
@@ -0,0 +1,88 @@
+package dhcpsvc
+
+import (
+ "net/netip"
+ "testing"
+
+ "github.com/google/gopacket/layers"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestIPv4Config_Options(t *testing.T) {
+ oneIP, otherIP := netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("5.6.7.8")
+ subnetMask := netip.MustParseAddr("255.255.0.0")
+
+ opt1 := layers.NewDHCPOption(layers.DHCPOptSubnetMask, subnetMask.AsSlice())
+ opt6 := layers.NewDHCPOption(layers.DHCPOptDNS, append(oneIP.AsSlice(), otherIP.AsSlice()...))
+ opt28 := layers.NewDHCPOption(layers.DHCPOptBroadcastAddr, oneIP.AsSlice())
+ opt121 := layers.NewDHCPOption(layers.DHCPOptClasslessStaticRoute, []byte("cba"))
+
+ testCases := []struct {
+ name string
+ conf *IPv4Config
+ wantExplicit layers.DHCPOptions
+ }{{
+ name: "all_default",
+ conf: &IPv4Config{
+ Options: nil,
+ },
+ wantExplicit: nil,
+ }, {
+ name: "configured_ip",
+ conf: &IPv4Config{
+ Options: layers.DHCPOptions{opt28},
+ },
+ wantExplicit: layers.DHCPOptions{opt28},
+ }, {
+ name: "configured_ips",
+ conf: &IPv4Config{
+ Options: layers.DHCPOptions{opt6},
+ },
+ wantExplicit: layers.DHCPOptions{opt6},
+ }, {
+ name: "configured_del",
+ conf: &IPv4Config{
+ Options: layers.DHCPOptions{
+ layers.NewDHCPOption(layers.DHCPOptBroadcastAddr, nil),
+ },
+ },
+ wantExplicit: nil,
+ }, {
+ name: "rewritten_del",
+ conf: &IPv4Config{
+ Options: layers.DHCPOptions{
+ layers.NewDHCPOption(layers.DHCPOptBroadcastAddr, nil),
+ opt28,
+ },
+ },
+ wantExplicit: layers.DHCPOptions{opt28},
+ }, {
+ name: "configured_and_del",
+ conf: &IPv4Config{
+ Options: layers.DHCPOptions{
+ layers.NewDHCPOption(layers.DHCPOptClasslessStaticRoute, []byte("a")),
+ layers.NewDHCPOption(layers.DHCPOptClasslessStaticRoute, nil),
+ opt121,
+ },
+ },
+ wantExplicit: layers.DHCPOptions{opt121},
+ }, {
+ name: "replace_config_value",
+ conf: &IPv4Config{
+ SubnetMask: netip.MustParseAddr("255.255.255.0"),
+ Options: layers.DHCPOptions{opt1},
+ },
+ wantExplicit: layers.DHCPOptions{opt1},
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ imp, exp := tc.conf.options()
+ assert.Equal(t, tc.wantExplicit, exp)
+
+ for c := range exp {
+ assert.NotContains(t, imp, c)
+ }
+ })
+ }
+}
diff --git a/internal/dhcpsvc/v6.go b/internal/dhcpsvc/v6.go
index 8bdc1637..2dc832b6 100644
--- a/internal/dhcpsvc/v6.go
+++ b/internal/dhcpsvc/v6.go
@@ -5,7 +5,9 @@ import (
"net/netip"
"time"
+ "github.com/AdguardTeam/golibs/log"
"github.com/google/gopacket/layers"
+ "golang.org/x/exp/slices"
)
// IPv6Config is the interface-specific configuration for DHCPv6.
@@ -13,8 +15,10 @@ type IPv6Config struct {
// RangeStart is the first address in the range to assign to DHCP clients.
RangeStart netip.Addr
- // Options is the list of DHCP options to send to DHCP clients.
- Options layers.DHCPOptions
+ // Options is the list of DHCP options to send to DHCP clients. The options
+ // with zero length are treated as deletions of the corresponding options,
+ // either implicit or explicit.
+ Options layers.DHCPv6Options
// LeaseDuration is the TTL of a DHCP lease.
LeaseDuration time.Duration
@@ -58,6 +62,15 @@ type iface6 struct {
// name is the name of the interface.
name string
+ // implicitOpts are the DHCPv6 options listed in RFC 8415 (and others) and
+ // initialized with default values. It must not have intersections with
+ // explicitOpts.
+ implicitOpts layers.DHCPv6Options
+
+ // explicitOpts are the user-configured options. It must not have
+ // intersections with implicitOpts.
+ explicitOpts layers.DHCPv6Options
+
// leaseTTL is the time-to-live of dynamic leases on this interface.
leaseTTL time.Duration
@@ -78,11 +91,45 @@ func newIface6(name string, conf *IPv6Config) (i *iface6) {
return nil
}
- return &iface6{
+ i = &iface6{
name: name,
rangeStart: conf.RangeStart,
leaseTTL: conf.LeaseDuration,
raSLAACOnly: conf.RASLAACOnly,
raAllowSLAAC: conf.RAAllowSLAAC,
}
+ i.implicitOpts, i.explicitOpts = conf.options()
+
+ return i
+}
+
+// options returns the implicit and explicit options for the interface. The two
+// lists are disjoint and the implicit options are initialized with default
+// values.
+//
+// TODO(e.burkov): Add implicit options according to RFC.
+func (conf *IPv6Config) options() (implicit, explicit layers.DHCPv6Options) {
+ // Set default values of host configuration parameters listed in RFC 8415.
+ implicit = layers.DHCPv6Options{}
+ slices.SortFunc(implicit, compareV6OptionCodes)
+
+ // Set values for explicitly configured options.
+ for _, exp := range conf.Options {
+ i, found := slices.BinarySearchFunc(implicit, exp, compareV6OptionCodes)
+ if found {
+ implicit = slices.Delete(implicit, i, i+1)
+ }
+
+ explicit = append(explicit, exp)
+ }
+
+ log.Debug("dhcpsvc: v6: implicit options: %s", implicit)
+ log.Debug("dhcpsvc: v6: explicit options: %s", explicit)
+
+ return implicit, explicit
+}
+
+// compareV6OptionCodes compares option codes of a and b.
+func compareV6OptionCodes(a, b layers.DHCPv6Option) (res int) {
+ return int(a.Code) - int(b.Code)
}
diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go
index ec20096b..2a11773d 100644
--- a/internal/dnsforward/config.go
+++ b/internal/dnsforward/config.go
@@ -89,12 +89,8 @@ type Config struct {
// servers are not responding.
FallbackDNS []string `yaml:"fallback_dns"`
- // AllServers, if true, parallel queries to all configured upstream servers
- // are enabled.
- AllServers bool `yaml:"all_servers"`
-
- // FastestAddr, if true, use Fastest Address algorithm.
- FastestAddr bool `yaml:"fastest_addr"`
+ // UpstreamMode determines the logic through which upstreams will be used.
+ UpstreamMode UpstreamMode `yaml:"upstream_mode"`
// FastestTimeout replaces the default timeout for dialing IP addresses
// when FastestAddr is true.
@@ -114,11 +110,10 @@ type Config struct {
// BlockedHosts is the list of hosts that should be blocked.
BlockedHosts []string `yaml:"blocked_hosts"`
- // TrustedProxies is the list of IP addresses and CIDR networks to detect
- // proxy servers addresses the DoH requests from which should be handled.
- // The value of nil or an empty slice for this field makes Proxy not trust
- // any address.
- TrustedProxies []string `yaml:"trusted_proxies"`
+ // TrustedProxies is the list of CIDR networks with proxy servers addresses
+ // from which the DoH requests should be handled. The value of nil or an
+ // empty slice for this field makes Proxy not trust any address.
+ TrustedProxies []netutil.Prefix `yaml:"trusted_proxies"`
// DNS cache settings
@@ -154,7 +149,7 @@ type Config struct {
// MaxGoroutines is the max number of parallel goroutines for processing
// incoming requests.
- MaxGoroutines uint32 `yaml:"max_goroutines"`
+ MaxGoroutines uint `yaml:"max_goroutines"`
// HandleDDR, if true, handle DDR requests
HandleDDR bool `yaml:"handle_ddr"`
@@ -294,9 +289,21 @@ type ServerConfig struct {
ServePlainDNS bool
}
+// UpstreamMode is a enumeration of upstream mode representations. See
+// [proxy.UpstreamModeType].
+type UpstreamMode string
+
+const (
+ UpstreamModeLoadBalance UpstreamMode = "load_balance"
+ UpstreamModeParallel UpstreamMode = "parallel"
+ UpstreamModeFastestAddr UpstreamMode = "fastest_addr"
+)
+
// newProxyConfig creates and validates configuration for the main proxy.
func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
srvConf := s.conf
+ trustedPrefixes := netutil.UnembedPrefixes(srvConf.TrustedProxies)
+
conf = &proxy.Config{
HTTP3: srvConf.ServeHTTP3,
Ratelimit: int(srvConf.Ratelimit),
@@ -304,7 +311,7 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
RatelimitSubnetLenIPv6: srvConf.RatelimitSubnetLenIPv6,
RatelimitWhitelist: srvConf.RatelimitWhitelist,
RefuseAny: srvConf.RefuseAny,
- TrustedProxies: srvConf.TrustedProxies,
+ TrustedProxies: netutil.SliceSubnetSet(trustedPrefixes),
CacheMinTTL: srvConf.CacheMinTTL,
CacheMaxTTL: srvConf.CacheMaxTTL,
CacheOptimistic: srvConf.CacheOptimistic,
@@ -313,7 +320,7 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
RequestHandler: s.handleDNSRequest,
HTTPSServerName: aghhttp.UserAgent(),
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
- MaxGoroutines: int(srvConf.MaxGoroutines),
+ MaxGoroutines: srvConf.MaxGoroutines,
UseDNS64: srvConf.UseDNS64,
DNS64Prefs: srvConf.DNS64Prefixes,
}
@@ -323,18 +330,11 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
conf.EDNSAddr = net.IP(srvConf.EDNSClientSubnet.CustomIP.AsSlice())
}
- if srvConf.CacheSize != 0 {
- conf.CacheEnabled = true
- conf.CacheSizeBytes = int(srvConf.CacheSize)
+ err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, srvConf.FastestTimeout.Duration)
+ if err != nil {
+ return nil, fmt.Errorf("upstream mode: %w", err)
}
- setProxyUpstreamMode(
- conf,
- srvConf.AllServers,
- srvConf.FastestAddr,
- srvConf.FastestTimeout.Duration,
- )
-
conf.BogusNXDomain, err = parseBogusNXDOMAIN(srvConf.BogusNXDomain)
if err != nil {
return nil, fmt.Errorf("bogus_nxdomain: %w", err)
@@ -361,6 +361,37 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
return nil, errors.Error("no default upstream servers configured")
}
+ conf, err = prepareCacheConfig(conf,
+ srvConf.CacheSize,
+ srvConf.CacheMinTTL,
+ srvConf.CacheMaxTTL,
+ )
+ if err != nil {
+ // Don't wrap the error since it's informative enough as is.
+ return nil, err
+ }
+
+ return conf, nil
+}
+
+// prepareCacheConfig prepares the cache configuration and returns an error if
+// there is one.
+func prepareCacheConfig(
+ conf *proxy.Config,
+ size uint32,
+ minTTL uint32,
+ maxTTL uint32,
+) (prepared *proxy.Config, err error) {
+ if size != 0 {
+ conf.CacheEnabled = true
+ conf.CacheSizeBytes = int(size)
+ }
+
+ err = validateCacheTTL(minTTL, maxTTL)
+ if err != nil {
+ return nil, fmt.Errorf("validating cache ttl: %w", err)
+ }
+
return conf, nil
}
@@ -739,3 +770,19 @@ func (s *Server) enableProtectionAfterPause() {
log.Info("dns: protection is restarted after pause")
}
+
+// validateCacheTTL returns an error if the configuration of the cache TTL
+// invalid.
+//
+// TODO(s.chzhen): Move to dnsproxy.
+func validateCacheTTL(minTTL, maxTTL uint32) (err error) {
+ if minTTL == 0 && maxTTL == 0 {
+ return nil
+ }
+
+ if maxTTL > 0 && minTTL > maxTTL {
+ return errors.Error("cache_ttl_min must be less than or equal to cache_ttl_max")
+ }
+
+ return nil
+}
diff --git a/internal/dnsforward/dns64_test.go b/internal/dnsforward/dns64_test.go
index 55c08db7..ad89098c 100644
--- a/internal/dnsforward/dns64_test.go
+++ b/internal/dnsforward/dns64_test.go
@@ -290,6 +290,7 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) {
TCPListenAddrs: []*net.TCPAddr{{}},
UseDNS64: true,
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go
index 8afbd3df..2da21391 100644
--- a/internal/dnsforward/dnsforward.go
+++ b/internal/dnsforward/dnsforward.go
@@ -81,6 +81,7 @@ type DHCP interface {
Enabled() (ok bool)
}
+// SystemResolvers is an interface for accessing the OS-provided resolvers.
type SystemResolvers interface {
// Addrs returns the list of system resolvers' addresses.
Addrs() (addrs []netip.AddrPort)
@@ -142,7 +143,7 @@ type Server struct {
// PTR resolving.
sysResolvers SystemResolvers
- // etcHosts contains the data from the system's hosts files.
+ // etcHosts contains the current data from the system's hosts files.
etcHosts upstream.Resolver
// bootstrap is the resolver for upstreams' hostnames.
@@ -239,6 +240,11 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
p.Anonymizer = aghnet.NewIPMut(nil)
}
+ var etcHosts upstream.Resolver
+ if p.EtcHosts != nil {
+ etcHosts = upstream.NewHostsResolver(p.EtcHosts)
+ }
+
s = &Server{
dnsFilter: p.DNSFilter,
dhcpServer: p.DHCPServer,
@@ -247,6 +253,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
privateNets: p.PrivateNets,
// TODO(e.burkov): Use some case-insensitive string comparison.
localDomainSuffix: strings.ToLower(localDomainSuffix),
+ etcHosts: etcHosts,
recDetector: newRecursionDetector(recursionTTL, cachedRecurrentReqNum),
clientIDCache: cache.New(cache.Config{
EnableLRU: true,
@@ -257,9 +264,6 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
ServePlainDNS: true,
},
}
- if p.EtcHosts != nil {
- s.etcHosts = p.EtcHosts
- }
s.sysResolvers, err = sysresolv.NewSystemResolvers(nil, defaultPlainDNSPort)
if err != nil {
@@ -307,7 +311,7 @@ func (s *Server) WriteDiskConfig(c *Config) {
c.AllowedClients = stringutil.CloneSlice(sc.AllowedClients)
c.DisallowedClients = stringutil.CloneSlice(sc.DisallowedClients)
c.BlockedHosts = stringutil.CloneSlice(sc.BlockedHosts)
- c.TrustedProxies = stringutil.CloneSlice(sc.TrustedProxies)
+ c.TrustedProxies = slices.Clone(sc.TrustedProxies)
c.UpstreamDNS = stringutil.CloneSlice(sc.UpstreamDNS)
}
@@ -386,7 +390,7 @@ func (s *Server) Exchange(ip netip.Addr) (host string, ttl time.Duration, err er
var resolver *proxy.Proxy
var errMsg string
- if s.privateNets.Contains(ip.AsSlice()) {
+ if s.privateNets.Contains(ip) {
if !s.conf.UsePrivateRDNS {
return "", 0, nil
}
@@ -466,13 +470,15 @@ func (s *Server) startLocked() error {
return err
}
-// setupLocalResolvers initializes the resolvers for local addresses. It
-// assumes s.serverLock is locked or the Server not running.
-func (s *Server) setupLocalResolvers(boot upstream.Resolver) (err error) {
+// prepareLocalResolvers initializes the local upstreams configuration using
+// boot as bootstrap. It assumes that s.serverLock is locked or s not running.
+func (s *Server) prepareLocalResolvers(
+ boot upstream.Resolver,
+) (uc *proxy.UpstreamConfig, err error) {
set, err := s.conf.ourAddrsSet()
if err != nil {
// Don't wrap the error because it's informative enough as is.
- return err
+ return nil, err
}
resolvers := s.conf.LocalPTRResolvers
@@ -489,29 +495,46 @@ func (s *Server) setupLocalResolvers(boot upstream.Resolver) (err error) {
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", resolvers)
- uc, err := s.prepareUpstreamConfig(resolvers, nil, &upstream.Options{
+ uc, err = s.prepareUpstreamConfig(resolvers, nil, &upstream.Options{
Bootstrap: boot,
Timeout: defaultLocalTimeout,
// TODO(e.burkov): Should we verify server's certificates?
PreferIPv6: s.conf.BootstrapPreferIPv6,
})
if err != nil {
- return fmt.Errorf("preparing private upstreams: %w", err)
+ return nil, fmt.Errorf("preparing private upstreams: %w", err)
}
if confNeedsFiltering {
err = filterOutAddrs(uc, set)
if err != nil {
- return fmt.Errorf("filtering private upstreams: %w", err)
+ return nil, fmt.Errorf("filtering private upstreams: %w", err)
}
}
+ return uc, nil
+}
+
+// setupLocalResolvers initializes and sets the resolvers for local addresses.
+// It assumes s.serverLock is locked or s not running.
+func (s *Server) setupLocalResolvers(boot upstream.Resolver) (err error) {
+ uc, err := s.prepareLocalResolvers(boot)
+ if err != nil {
+ // Don't wrap the error because it's informative enough as is.
+ return err
+ }
+
s.localResolvers = &proxy.Proxy{
Config: proxy.Config{
UpstreamConfig: uc,
},
}
+ err = s.localResolvers.Init()
+ if err != nil {
+ return fmt.Errorf("initializing proxy: %w", err)
+ }
+
// TODO(e.burkov): Should we also consider the DNS64 usage?
if s.conf.UsePrivateRDNS &&
// Only set the upstream config if there are any upstreams. It's safe
@@ -697,15 +720,13 @@ func (s *Server) prepareInternalProxy() (err error) {
CacheEnabled: true,
CacheSizeBytes: 4096,
UpstreamConfig: srvConf.UpstreamConfig,
- MaxGoroutines: int(s.conf.MaxGoroutines),
+ MaxGoroutines: s.conf.MaxGoroutines,
}
- setProxyUpstreamMode(
- conf,
- srvConf.AllServers,
- srvConf.FastestAddr,
- srvConf.FastestTimeout.Duration,
- )
+ err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, srvConf.FastestTimeout.Duration)
+ if err != nil {
+ return fmt.Errorf("invalid upstream mode: %w", err)
+ }
// TODO(a.garipov): Make a proper constructor for proxy.Proxy.
p := &proxy.Proxy{
diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go
index 2c30ff6c..ece14ba3 100644
--- a/internal/dnsforward/dnsforward_test.go
+++ b/internal/dnsforward/dnsforward_test.go
@@ -177,6 +177,7 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte)
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
@@ -305,6 +306,7 @@ func TestServer(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
@@ -344,6 +346,7 @@ func TestServer_timeout(t *testing.T) {
srvConf := &ServerConfig{
UpstreamTimeout: testTimeout,
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
@@ -362,6 +365,7 @@ func TestServer_timeout(t *testing.T) {
s, err := NewServer(DNSCreateParams{DNSFilter: createTestDNSFilter(t)})
require.NoError(t, err)
+ s.conf.Config.UpstreamMode = UpstreamModeLoadBalance
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{
Enabled: false,
}
@@ -379,6 +383,7 @@ func TestServer_Prepare_fallbacks(t *testing.T) {
"#tls://1.1.1.1",
"8.8.8.8",
},
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
@@ -401,6 +406,7 @@ func TestServerWithProtectionDisabled(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
@@ -478,7 +484,8 @@ func TestServerRace(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
- UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
+ UpstreamMode: UpstreamModeLoadBalance,
+ UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
},
ConfigModified: func() {},
ServePlainDNS: true,
@@ -531,6 +538,7 @@ func TestSafeSearch(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -547,32 +555,41 @@ func TestSafeSearch(t *testing.T) {
googleIP, _ := aghtest.HostToIPs("forcesafesearch.google.com")
testCases := []struct {
- host string
- want netip.Addr
+ host string
+ want netip.Addr
+ wantCNAME string
}{{
- host: "yandex.com.",
- want: yandexIP,
+ host: "yandex.com.",
+ want: yandexIP,
+ wantCNAME: "",
}, {
- host: "yandex.by.",
- want: yandexIP,
+ host: "yandex.by.",
+ want: yandexIP,
+ wantCNAME: "",
}, {
- host: "yandex.kz.",
- want: yandexIP,
+ host: "yandex.kz.",
+ want: yandexIP,
+ wantCNAME: "",
}, {
- host: "yandex.ru.",
- want: yandexIP,
+ host: "yandex.ru.",
+ want: yandexIP,
+ wantCNAME: "",
}, {
- host: "www.google.com.",
- want: googleIP,
+ host: "www.google.com.",
+ want: googleIP,
+ wantCNAME: "forcesafesearch.google.com.",
}, {
- host: "www.google.com.af.",
- want: googleIP,
+ host: "www.google.com.af.",
+ want: googleIP,
+ wantCNAME: "forcesafesearch.google.com.",
}, {
- host: "www.google.be.",
- want: googleIP,
+ host: "www.google.be.",
+ want: googleIP,
+ wantCNAME: "forcesafesearch.google.com.",
}, {
- host: "www.google.by.",
- want: googleIP,
+ host: "www.google.by.",
+ want: googleIP,
+ wantCNAME: "forcesafesearch.google.com.",
}}
for _, tc := range testCases {
@@ -582,7 +599,18 @@ func TestSafeSearch(t *testing.T) {
var reply *dns.Msg
reply, _, err = client.Exchange(req, addr)
require.NoErrorf(t, err, "couldn't talk to server %s: %s", addr, err)
- assertResponse(t, reply, tc.want)
+
+ if tc.wantCNAME != "" {
+ require.Len(t, reply.Answer, 2)
+
+ cname := testutil.RequireTypeAssert[*dns.CNAME](t, reply.Answer[0])
+ assert.Equal(t, tc.wantCNAME, cname.Target)
+ } else {
+ require.Len(t, reply.Answer, 1)
+ }
+
+ a := testutil.RequireTypeAssert[*dns.A](t, reply.Answer[len(reply.Answer)-1])
+ assert.Equal(t, net.IP(tc.want.AsSlice()), a.A)
})
}
}
@@ -594,6 +622,7 @@ func TestInvalidRequest(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -623,6 +652,7 @@ func TestBlockedRequest(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -658,7 +688,8 @@ func TestServerCustomClientUpstream(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
- CacheSize: defaultCacheSize,
+ CacheSize: defaultCacheSize,
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -736,6 +767,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -769,6 +801,7 @@ func TestBlockCNAME(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -844,6 +877,7 @@ func TestClientRulesForCNAMEMatching(t *testing.T) {
FilterHandler: func(_ netip.Addr, _ string, settings *filtering.Settings) {
settings.FilteringEnabled = false
},
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -889,6 +923,7 @@ func TestNullBlockedRequest(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -954,7 +989,8 @@ func TestBlockedCustomIP(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
- UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
+ UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -1007,6 +1043,7 @@ func TestBlockedByHosts(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -1058,6 +1095,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -1116,7 +1154,8 @@ func TestRewrite(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
- UpstreamDNS: []string{"8.8.8.8:53"},
+ UpstreamDNS: []string{"8.8.8.8:53"},
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
@@ -1245,6 +1284,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
+ s.conf.Config.UpstreamMode = UpstreamModeLoadBalance
err = s.Prepare(&s.conf)
require.NoError(t, err)
@@ -1327,6 +1367,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
+ s.conf.Config.UpstreamMode = UpstreamModeLoadBalance
err = s.Prepare(&s.conf)
require.NoError(t, err)
@@ -1506,9 +1547,9 @@ func TestServer_Exchange(t *testing.T) {
},
},
}
-
srv.conf.UsePrivateRDNS = true
srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
+ require.NoError(t, srv.internalProxy.Init())
testCases := []struct {
req netip.Addr
@@ -1584,6 +1625,7 @@ func TestServer_Exchange(t *testing.T) {
srv.localResolvers = &proxy.Proxy{
Config: pcfg,
}
+ require.NoError(t, srv.localResolvers.Init())
t.Run(tc.name, func(t *testing.T) {
host, ttl, eerr := srv.Exchange(tc.req)
diff --git a/internal/dnsforward/dnsrewrite_test.go b/internal/dnsforward/dnsrewrite_test.go
index 1022388f..5204c2f2 100644
--- a/internal/dnsforward/dnsrewrite_test.go
+++ b/internal/dnsforward/dnsrewrite_test.go
@@ -38,6 +38,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
BlockingMode: filtering.BlockingModeDefault,
}, ServerConfig{
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
diff --git a/internal/dnsforward/filter.go b/internal/dnsforward/filter.go
index e627122e..a92cccd6 100644
--- a/internal/dnsforward/filter.go
+++ b/internal/dnsforward/filter.go
@@ -95,7 +95,7 @@ func (s *Server) filterDNSRequest(dctx *dnsContext) (res *filtering.Result, err
dctx.origQuestion = q
req.Question[0].Name = dns.Fqdn(res.CanonName)
case res.Reason == filtering.Rewritten:
- pctx.Res = s.filterRewritten(req, host, res, q.Qtype)
+ pctx.Res = s.getCNAMEWithIPs(req, res.IPList, res.CanonName)
case res.Reason.In(filtering.RewrittenRule, filtering.RewrittenAutoHosts):
if err = s.filterDNSRewrite(req, res, pctx); err != nil {
return nil, err
@@ -105,37 +105,6 @@ func (s *Server) filterDNSRequest(dctx *dnsContext) (res *filtering.Result, err
return res, err
}
-// filterRewritten handles DNS rewrite filters. It returns a DNS response with
-// the data from the filtering result. All parameters must not be nil.
-func (s *Server) filterRewritten(
- req *dns.Msg,
- host string,
- res *filtering.Result,
- qt uint16,
-) (resp *dns.Msg) {
- resp = s.makeResponse(req)
- name := host
- if len(res.CanonName) != 0 {
- resp.Answer = append(resp.Answer, s.genAnswerCNAME(req, res.CanonName))
- name = res.CanonName
- }
-
- for _, ip := range res.IPList {
- switch qt {
- case dns.TypeA:
- a := s.genAnswerA(req, ip)
- a.Hdr.Name = dns.Fqdn(name)
- resp.Answer = append(resp.Answer, a)
- case dns.TypeAAAA:
- a := s.genAnswerAAAA(req, ip)
- a.Hdr.Name = dns.Fqdn(name)
- resp.Answer = append(resp.Answer, a)
- }
- }
-
- return resp
-}
-
// checkHostRules checks the host against filters. It is safe for concurrent
// use.
func (s *Server) checkHostRules(
diff --git a/internal/dnsforward/filter_test.go b/internal/dnsforward/filter_test.go
index 6559b308..9e172a32 100644
--- a/internal/dnsforward/filter_test.go
+++ b/internal/dnsforward/filter_test.go
@@ -31,6 +31,7 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go
index ac82ea76..d3d352df 100644
--- a/internal/dnsforward/http.go
+++ b/internal/dnsforward/http.go
@@ -70,7 +70,7 @@ type jsonDNSConfig struct {
DisableIPv6 *bool `json:"disable_ipv6"`
// UpstreamMode defines the way DNS requests are constructed.
- UpstreamMode *string `json:"upstream_mode"`
+ UpstreamMode *jsonUpstreamMode `json:"upstream_mode"`
// BlockedResponseTTL is the TTL for blocked responses.
BlockedResponseTTL *uint32 `json:"blocked_response_ttl"`
@@ -114,6 +114,21 @@ type jsonDNSConfig struct {
DefaultLocalPTRUpstreams []string `json:"default_local_ptr_upstreams,omitempty"`
}
+// jsonUpstreamMode is a enumeration of upstream modes.
+type jsonUpstreamMode string
+
+const (
+ // jsonUpstreamModeEmpty is the default value on frontend, it is used as
+ // jsonUpstreamModeLoadBalance mode.
+ //
+ // Deprecated: Use jsonUpstreamModeLoadBalance instead.
+ jsonUpstreamModeEmpty jsonUpstreamMode = ""
+
+ jsonUpstreamModeLoadBalance jsonUpstreamMode = "load_balance"
+ jsonUpstreamModeParallel jsonUpstreamMode = "parallel"
+ jsonUpstreamModeFastestAddr jsonUpstreamMode = "fastest_addr"
+)
+
func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
protectionEnabled, protectionDisabledUntil := s.UpdatedProtectionStatus()
@@ -145,11 +160,16 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
usePrivateRDNS := s.conf.UsePrivateRDNS
localPTRUpstreams := stringutil.CloneSliceOrEmpty(s.conf.LocalPTRResolvers)
- var upstreamMode string
- if s.conf.FastestAddr {
- upstreamMode = "fastest_addr"
- } else if s.conf.AllServers {
- upstreamMode = "parallel"
+ var upstreamMode jsonUpstreamMode
+ switch s.conf.UpstreamMode {
+ case UpstreamModeLoadBalance:
+ // TODO(d.kolyshev): Support jsonUpstreamModeLoadBalance on frontend instead
+ // of jsonUpstreamModeEmpty.
+ upstreamMode = jsonUpstreamModeEmpty
+ case UpstreamModeParallel:
+ upstreamMode = jsonUpstreamModeParallel
+ case UpstreamModeFastestAddr:
+ upstreamMode = jsonUpstreamModeFastestAddr
}
defPTRUps, err := s.defaultLocalPTRUpstreams()
@@ -222,18 +242,22 @@ func (req *jsonDNSConfig) checkBlockingMode() (err error) {
return validateBlockingMode(*req.BlockingMode, req.BlockingIPv4, req.BlockingIPv6)
}
-// checkUpstreamsMode returns an error if the upstream mode is invalid.
-func (req *jsonDNSConfig) checkUpstreamsMode() (err error) {
+// checkUpstreamMode returns an error if the upstream mode is invalid.
+func (req *jsonDNSConfig) checkUpstreamMode() (err error) {
if req.UpstreamMode == nil {
return nil
}
- mode := *req.UpstreamMode
- if ok := slices.Contains([]string{"", "fastest_addr", "parallel"}, mode); !ok {
- return fmt.Errorf("upstream_mode: incorrect value %q", mode)
+ switch um := *req.UpstreamMode; um {
+ case
+ jsonUpstreamModeEmpty,
+ jsonUpstreamModeLoadBalance,
+ jsonUpstreamModeParallel,
+ jsonUpstreamModeFastestAddr:
+ return nil
+ default:
+ return fmt.Errorf("upstream_mode: incorrect value %q", um)
}
-
- return nil
}
// checkBootstrap returns an error if any bootstrap address is invalid.
@@ -243,17 +267,22 @@ func (req *jsonDNSConfig) checkBootstrap() (err error) {
}
var b string
- defer func() { err = errors.Annotate(err, "checking bootstrap %s: invalid address: %w", b) }()
+ defer func() { err = errors.Annotate(err, "checking bootstrap %s: %w", b) }()
for _, b = range *req.Bootstraps {
if b == "" {
return errors.Error("empty")
}
- if _, err = upstream.NewUpstreamResolver(b, nil); err != nil {
+ var resolver *upstream.UpstreamResolver
+ if resolver, err = upstream.NewUpstreamResolver(b, nil); err != nil {
// Don't wrap the error because it's informative enough as is.
return err
}
+
+ if err = resolver.Close(); err != nil {
+ return fmt.Errorf("closing %s: %w", b, err)
+ }
}
return nil
@@ -297,7 +326,7 @@ func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) {
return err
}
- err = req.checkUpstreamsMode()
+ err = req.checkUpstreamMode()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
@@ -354,15 +383,12 @@ func (req *jsonDNSConfig) checkCacheTTL() (err error) {
if req.CacheMinTTL != nil {
minTTL = *req.CacheMinTTL
}
+
if req.CacheMaxTTL != nil {
maxTTL = *req.CacheMaxTTL
}
- if minTTL <= maxTTL {
- return nil
- }
-
- return errors.Error("cache_ttl_min must be less or equal than cache_ttl_max")
+ return validateCacheTTL(minTTL, maxTTL)
}
// checkRatelimitSubnetMaskLen returns an error if the length of the subnet mask
@@ -446,8 +472,9 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) {
}
if dc.UpstreamMode != nil {
- s.conf.AllServers = *dc.UpstreamMode == "parallel"
- s.conf.FastestAddr = *dc.UpstreamMode == "fastest_addr"
+ s.conf.UpstreamMode = mustParseUpstreamMode(*dc.UpstreamMode)
+ } else {
+ s.conf.UpstreamMode = UpstreamModeLoadBalance
}
if dc.EDNSCSUseCustom != nil && *dc.EDNSCSUseCustom {
@@ -460,6 +487,22 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) {
return s.setConfigRestartable(dc)
}
+// mustParseUpstreamMode returns an upstream mode parsed from jsonUpstreamMode.
+// Panics in case of invalid value.
+func mustParseUpstreamMode(mode jsonUpstreamMode) (um UpstreamMode) {
+ switch mode {
+ case jsonUpstreamModeEmpty, jsonUpstreamModeLoadBalance:
+ return UpstreamModeLoadBalance
+ case jsonUpstreamModeParallel:
+ return UpstreamModeParallel
+ case jsonUpstreamModeFastestAddr:
+ return UpstreamModeFastestAddr
+ default:
+ // Should never happen, since the value should be validated.
+ panic(fmt.Errorf("unexpected upstream mode: %q", mode))
+ }
+}
+
// setIfNotNil sets the value pointed at by currentPtr to the value pointed at
// by newPtr if newPtr is not nil. currentPtr must not be nil.
func setIfNotNil[T any](currentPtr, newPtr *T) (hasSet bool) {
diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go
index b7696cdc..2e28039f 100644
--- a/internal/dnsforward/http_test.go
+++ b/internal/dnsforward/http_test.go
@@ -20,6 +20,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
+ "github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
@@ -76,6 +77,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
FallbackDNS: []string{"9.9.9.10"},
RatelimitSubnetLenIPv4: 24,
RatelimitSubnetLenIPv6: 56,
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ConfigModified: func() {},
@@ -102,7 +104,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
}, {
conf: func() ServerConfig {
conf := defaultConf
- conf.FastestAddr = true
+ conf.UpstreamMode = UpstreamModeFastestAddr
return conf
},
@@ -110,7 +112,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
}, {
conf: func() ServerConfig {
conf := defaultConf
- conf.AllServers = true
+ conf.UpstreamMode = UpstreamModeParallel
return conf
},
@@ -156,6 +158,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
RatelimitSubnetLenIPv4: 24,
RatelimitSubnetLenIPv6: 56,
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ConfigModified: func() {},
@@ -224,11 +227,11 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
`upstream servers: validating upstream "!!!": not an ip:port`,
}, {
name: "bootstraps_bad",
- wantSet: `validating dns config: checking bootstrap a: invalid address: not a bootstrap: ` +
- `ParseAddr("a"): unable to parse IP`,
+ wantSet: `validating dns config: checking bootstrap a: not a bootstrap: ParseAddr("a"): ` +
+ `unable to parse IP`,
}, {
name: "cache_bad_ttl",
- wantSet: `validating dns config: cache_ttl_min must be less or equal than cache_ttl_max`,
+ wantSet: `validating dns config: cache_ttl_min must be less than or equal to cache_ttl_max`,
}, {
name: "upstream_mode_bad",
wantSet: `validating dns config: upstream_mode: incorrect value "somethingelse"`,
@@ -522,11 +525,12 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
TCPListenAddrs: []*net.TCPAddr{{}},
UpstreamTimeout: upsTimeout,
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
}, nil)
- srv.etcHosts = hc
+ srv.etcHosts = upstream.NewHostsResolver(hc)
startDeferStop(t, srv)
testCases := []struct {
diff --git a/internal/dnsforward/msg.go b/internal/dnsforward/msg.go
index 6685c861..eeb2c963 100644
--- a/internal/dnsforward/msg.go
+++ b/internal/dnsforward/msg.go
@@ -66,12 +66,46 @@ func (s *Server) genDNSFilterMessage(
// If Safe Search generated the necessary IP addresses, use them.
// Otherwise, if there were no errors, there are no addresses for the
// requested IP version, so produce a NODATA response.
- return s.genResponseWithIPs(req, ipsFromRules(res.Rules))
+ return s.getCNAMEWithIPs(req, ipsFromRules(res.Rules), res.CanonName)
default:
return s.genForBlockingMode(req, ipsFromRules(res.Rules))
}
}
+// getCNAMEWithIPs generates a filtered response to req for with CNAME record
+// and provided ips.
+func (s *Server) getCNAMEWithIPs(req *dns.Msg, ips []netip.Addr, cname string) (resp *dns.Msg) {
+ resp = s.makeResponse(req)
+
+ originalName := req.Question[0].Name
+
+ var ans []dns.RR
+ if cname != "" {
+ ans = append(ans, s.genAnswerCNAME(req, cname))
+
+ // The given IPs actually are resolved for this cname.
+ req.Question[0].Name = dns.Fqdn(cname)
+ defer func() { req.Question[0].Name = originalName }()
+ }
+
+ switch req.Question[0].Qtype {
+ case dns.TypeA:
+ ans = append(ans, s.genAnswersWithIPv4s(req, ips)...)
+ case dns.TypeAAAA:
+ for _, ip := range ips {
+ if ip.Is6() {
+ ans = append(ans, s.genAnswerAAAA(req, ip))
+ }
+ }
+ default:
+ // Go on and return an empty response.
+ }
+
+ resp.Answer = ans
+
+ return resp
+}
+
// genForBlockingMode generates a filtered response to req based on the server's
// blocking mode.
func (s *Server) genForBlockingMode(req *dns.Msg, ips []netip.Addr) (resp *dns.Msg) {
diff --git a/internal/dnsforward/process.go b/internal/dnsforward/process.go
index c9aea322..8cfe923a 100644
--- a/internal/dnsforward/process.go
+++ b/internal/dnsforward/process.go
@@ -36,11 +36,8 @@ type dnsContext struct {
// unreversedReqIP stores an IP address obtained from a PTR request if it
// was parsed successfully and belongs to one of the locally served IP
- // ranges. It is also filled with unmapped version of the address if it's
- // within DNS64 prefixes.
- //
- // TODO(e.burkov): Use netip.Addr when we switch to netip more fully.
- unreversedReqIP net.IP
+ // ranges.
+ unreversedReqIP netip.Addr
// err is the error returned from a processing function.
err error
@@ -350,7 +347,7 @@ func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
rc = resultCodeSuccess
- dctx.isLocalClient = s.privateNets.Contains(dctx.proxyCtx.Addr.Addr().AsSlice())
+ dctx.isLocalClient = s.privateNets.Contains(dctx.proxyCtx.Addr.Addr())
return rc
}
@@ -491,14 +488,7 @@ func extractARPASubnet(domain string) (pref netip.Prefix, err error) {
}
}
- var subnet *net.IPNet
- subnet, err = netutil.SubnetFromReversedAddr(domain[idx:])
- if err != nil {
- // Don't wrap the error since it's informative enough as is.
- return netip.Prefix{}, err
- }
-
- return netutil.IPNetToPrefixNoMapped(subnet)
+ return netutil.PrefixFromReversedAddr(domain[idx:])
}
// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
@@ -532,8 +522,7 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
// assume that all the DHCP leases we give are locally served or at least
// shouldn't be accessible externally.
subnetAddr := subnet.Addr()
- addrData := subnetAddr.AsSlice()
- if !s.privateNets.Contains(addrData) {
+ if !s.privateNets.Contains(subnetAddr) {
return resultCodeSuccess
}
@@ -548,7 +537,7 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
}
// Do not perform unreversing ever again.
- dctx.unreversedReqIP = addrData
+ dctx.unreversedReqIP = subnetAddr
// There is no need to filter request from external addresses since this
// code is only executed when the request is for locally served ARPA
@@ -573,16 +562,8 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
- ip := dctx.unreversedReqIP
- if ip == nil {
- return resultCodeSuccess
- }
-
- // TODO(a.garipov): Remove once we switch to [netip.Addr] more fully.
- ipAddr, err := netutil.IPToAddrNoMapped(ip)
- if err != nil {
- log.Debug("dnsforward: bad reverse ip %v from dhcp: %s", ip, err)
-
+ ipAddr := dctx.unreversedReqIP
+ if ipAddr == (netip.Addr{}) {
return resultCodeSuccess
}
@@ -591,7 +572,7 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
- log.Debug("dnsforward: dhcp client %s is %q", ip, host)
+ log.Debug("dnsforward: dhcp client %s is %q", ipAddr, host)
req := pctx.Req
resp := s.makeResponse(req)
@@ -624,7 +605,7 @@ func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
}
ip := dctx.unreversedReqIP
- if ip == nil {
+ if ip == (netip.Addr{}) {
return resultCodeSuccess
}
@@ -639,8 +620,7 @@ func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
// Generate the server failure if the private upstream configuration
// is empty.
//
- // TODO(e.burkov): Get rid of this crutch once the local resolvers
- // logic is moved to the dnsproxy completely.
+ // This is a crutch, see TODO at [Server.localResolvers].
if errors.Is(err, upstream.ErrNoUpstreams) {
pctx.Res = s.genServerFailure(pctx.Req)
diff --git a/internal/dnsforward/process_internal_test.go b/internal/dnsforward/process_internal_test.go
index 18b04b3f..2c919d7d 100644
--- a/internal/dnsforward/process_internal_test.go
+++ b/internal/dnsforward/process_internal_test.go
@@ -79,6 +79,7 @@ func TestServer_ProcessInitial(t *testing.T) {
c := ServerConfig{
Config: Config{
AAAADisabled: tc.aaaaDisabled,
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
@@ -179,6 +180,7 @@ func TestServer_ProcessFilteringAfterResponse(t *testing.T) {
c := ServerConfig{
Config: Config{
AAAADisabled: tc.aaaaDisabled,
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
@@ -694,6 +696,7 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
// TODO(s.chzhen): Add tests where EDNSClientSubnet.Enabled is true.
// Improve Config declaration for tests.
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
@@ -770,6 +773,7 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
@@ -791,7 +795,7 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
}
dnsCtx = &dnsContext{
proxyCtx: proxyCtx,
- unreversedReqIP: net.IP{192, 168, 1, 1},
+ unreversedReqIP: netip.MustParseAddr("192.168.1.1"),
}
s.conf.UsePrivateRDNS = use
}
diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go
index f67e5ad8..6a6b9853 100644
--- a/internal/dnsforward/stats.go
+++ b/internal/dnsforward/stats.go
@@ -25,10 +25,10 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
ip := pctx.Addr.Addr().AsSlice()
s.anonymizer.Load()(ip)
+ ipStr := net.IP(ip).String()
- log.Debug("dnsforward: client ip for stats and querylog: %s", ip)
+ log.Debug("dnsforward: client ip for stats and querylog: %s", ipStr)
- ipStr := pctx.Addr.Addr().String()
ids := []string{ipStr, dctx.clientID}
qt, cl := q.Qtype, q.Qclass
diff --git a/internal/dnsforward/svcbmsg_test.go b/internal/dnsforward/svcbmsg_test.go
index 58275ef4..2c2b7b0b 100644
--- a/internal/dnsforward/svcbmsg_test.go
+++ b/internal/dnsforward/svcbmsg_test.go
@@ -17,6 +17,7 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) {
BlockingMode: filtering.BlockingModeDefault,
}, ServerConfig{
Config: Config{
+ UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ServePlainDNS: true,
diff --git a/internal/dnsforward/upstreams.go b/internal/dnsforward/upstreams.go
index 3f877ac7..5fed582a 100644
--- a/internal/dnsforward/upstreams.go
+++ b/internal/dnsforward/upstreams.go
@@ -136,18 +136,22 @@ func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
// based on provided parameters.
func setProxyUpstreamMode(
conf *proxy.Config,
- allServers bool,
- fastestAddr bool,
+ upstreamMode UpstreamMode,
fastestTimeout time.Duration,
-) {
- if allServers {
+) (err error) {
+ switch upstreamMode {
+ case UpstreamModeParallel:
conf.UpstreamMode = proxy.UModeParallel
- } else if fastestAddr {
+ case UpstreamModeFastestAddr:
conf.UpstreamMode = proxy.UModeFastestAddr
conf.FastestPingTimeout = fastestTimeout
- } else {
+ case UpstreamModeLoadBalance:
conf.UpstreamMode = proxy.UModeLoadBalance
+ default:
+ return fmt.Errorf("unexpected value %q", upstreamMode)
}
+
+ return nil
}
// createBootstrap returns a bootstrap resolver based on the configuration of s.
@@ -176,7 +180,7 @@ func (s *Server) createBootstrap(
var parallel upstream.ParallelResolver
for _, b := range boots {
- parallel = append(parallel, b)
+ parallel = append(parallel, upstream.NewCachingResolver(b))
}
if s.etcHosts != nil {
@@ -294,7 +298,7 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet)
continue
}
- if !privateNets.Contains(subnet.Addr().AsSlice()) {
+ if !privateNets.Contains(subnet.Addr()) {
errs = append(
errs,
fmt.Errorf("arpa domain %q should point to a locally-served network", domain),
diff --git a/internal/filtering/dnsrewrite.go b/internal/filtering/dnsrewrite.go
index 3fd6e778..19b964a2 100644
--- a/internal/filtering/dnsrewrite.go
+++ b/internal/filtering/dnsrewrite.go
@@ -1,7 +1,6 @@
package filtering
import (
- "github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
@@ -95,39 +94,3 @@ func (d *DNSFilter) processDNSResultRewrites(
return res
}
-
-// appendRewriteResultFromHost appends the rewrite result from rec to vals and
-// resRules.
-func appendRewriteResultFromHost(
- vals []rules.RRValue,
- resRules []*ResultRule,
- rec *hostsfile.Record,
- qtype uint16,
-) (updatedVals []rules.RRValue, updatedRules []*ResultRule) {
- switch qtype {
- case dns.TypeA:
- if !rec.Addr.Is4() {
- return vals, resRules
- }
-
- vals = append(vals, rec.Addr)
- case dns.TypeAAAA:
- if !rec.Addr.Is6() {
- return vals, resRules
- }
-
- vals = append(vals, rec.Addr)
- case dns.TypePTR:
- for _, name := range rec.Names {
- vals = append(vals, name)
- }
- }
-
- recText, _ := rec.MarshalText()
- resRules = append(resRules, &ResultRule{
- FilterListID: SysHostsListID,
- Text: string(recText),
- })
-
- return vals, resRules
-}
diff --git a/internal/filtering/dnsrewrite_test.go b/internal/filtering/dnsrewrite_test.go
index 98853c95..06cd921b 100644
--- a/internal/filtering/dnsrewrite_test.go
+++ b/internal/filtering/dnsrewrite_test.go
@@ -1,17 +1,11 @@
package filtering
import (
- "fmt"
"net/netip"
"path"
"testing"
- "testing/fstest"
- "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
- "github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/netutil"
- "github.com/AdguardTeam/golibs/testutil"
- "github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -215,154 +209,3 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
assert.Equal(t, "new-ptr-with-dot.", ptr)
})
}
-
-func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
- addrv4 := netip.MustParseAddr("1.2.3.4")
- addrv6 := netip.MustParseAddr("::1")
- addrMapped := netip.MustParseAddr("::ffff:1.2.3.4")
-
- data := fmt.Sprintf(
- ""+
- "%s v4.host.example\n"+
- "%s v6.host.example\n"+
- "%s mapped.host.example\n",
- addrv4,
- addrv6,
- addrMapped,
- )
-
- files := fstest.MapFS{
- "hosts": &fstest.MapFile{
- Data: []byte(data),
- },
- }
- watcher := &aghtest.FSWatcher{
- OnEvents: func() (e <-chan struct{}) { return nil },
- OnAdd: func(name string) (err error) { return nil },
- OnClose: func() (err error) { return nil },
- }
- hc, err := aghnet.NewHostsContainer(files, watcher, "hosts")
- require.NoError(t, err)
- testutil.CleanupAndRequireSuccess(t, hc.Close)
-
- f, _ := newForTest(t, &Config{EtcHosts: hc}, nil)
- setts := &Settings{
- FilteringEnabled: true,
- }
-
- testCases := []struct {
- name string
- host string
- wantRules []*ResultRule
- wantResps []rules.RRValue
- dtyp uint16
- }{{
- name: "v4",
- host: "v4.host.example",
- dtyp: dns.TypeA,
- wantRules: []*ResultRule{{
- Text: "1.2.3.4 v4.host.example",
- FilterListID: SysHostsListID,
- }},
- wantResps: []rules.RRValue{addrv4},
- }, {
- name: "v6",
- host: "v6.host.example",
- dtyp: dns.TypeAAAA,
- wantRules: []*ResultRule{{
- Text: "::1 v6.host.example",
- FilterListID: SysHostsListID,
- }},
- wantResps: []rules.RRValue{addrv6},
- }, {
- name: "mapped",
- host: "mapped.host.example",
- dtyp: dns.TypeAAAA,
- wantRules: []*ResultRule{{
- Text: "::ffff:1.2.3.4 mapped.host.example",
- FilterListID: SysHostsListID,
- }},
- wantResps: []rules.RRValue{addrMapped},
- }, {
- name: "ptr",
- host: "4.3.2.1.in-addr.arpa",
- dtyp: dns.TypePTR,
- wantRules: []*ResultRule{{
- Text: "1.2.3.4 v4.host.example",
- FilterListID: SysHostsListID,
- }},
- wantResps: []rules.RRValue{"v4.host.example"},
- }, {
- name: "ptr-mapped",
- host: "4.0.3.0.2.0.1.0.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
- dtyp: dns.TypePTR,
- wantRules: []*ResultRule{{
- Text: "::ffff:1.2.3.4 mapped.host.example",
- FilterListID: SysHostsListID,
- }},
- wantResps: []rules.RRValue{"mapped.host.example"},
- }, {
- name: "not_found_v4",
- host: "non.existent.example",
- dtyp: dns.TypeA,
- wantRules: nil,
- wantResps: nil,
- }, {
- name: "not_found_v6",
- host: "non.existent.example",
- dtyp: dns.TypeAAAA,
- wantRules: nil,
- wantResps: nil,
- }, {
- name: "not_found_ptr",
- host: "4.3.2.2.in-addr.arpa",
- dtyp: dns.TypePTR,
- wantRules: nil,
- wantResps: nil,
- }, {
- name: "v4_mismatch",
- host: "v4.host.example",
- dtyp: dns.TypeAAAA,
- wantRules: nil,
- wantResps: nil,
- }, {
- name: "v6_mismatch",
- host: "v6.host.example",
- dtyp: dns.TypeA,
- wantRules: nil,
- wantResps: nil,
- }, {
- name: "wrong_ptr",
- host: "4.3.2.1.ip6.arpa",
- dtyp: dns.TypePTR,
- wantRules: nil,
- wantResps: nil,
- }, {
- name: "unsupported_type",
- host: "v4.host.example",
- dtyp: dns.TypeCNAME,
- wantRules: nil,
- wantResps: nil,
- }}
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- var res Result
- res, err = f.CheckHost(tc.host, tc.dtyp, setts)
- require.NoError(t, err)
-
- if len(tc.wantRules) == 0 {
- assert.Empty(t, res.Rules)
- assert.Nil(t, res.DNSRewriteResult)
-
- return
- }
-
- require.NotNil(t, res.DNSRewriteResult)
- require.Contains(t, res.DNSRewriteResult.Response, tc.dtyp)
-
- assert.Equal(t, tc.wantResps, res.DNSRewriteResult.Response[tc.dtyp])
- assert.Equal(t, tc.wantRules, res.Rules)
- })
- }
-}
diff --git a/internal/filtering/filtering.go b/internal/filtering/filtering.go
index 703f6c71..2d382530 100644
--- a/internal/filtering/filtering.go
+++ b/internal/filtering/filtering.go
@@ -18,13 +18,11 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
- "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
- "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/golibs/syncutil"
"github.com/AdguardTeam/urlfilter"
@@ -100,7 +98,7 @@ type Config struct {
// system configuration files (e.g. /etc/hosts).
//
// TODO(e.burkov): Move it to dnsforward entirely.
- EtcHosts *aghnet.HostsContainer `yaml:"-"`
+ EtcHosts hostsfile.Storage `yaml:"-"`
// Called when the configuration is changed by HTTP request
ConfigModified func() `yaml:"-"`
@@ -482,15 +480,6 @@ func (d *DNSFilter) SetProtectionEnabled(status bool) {
d.conf.ProtectionEnabled = status
}
-// EtcHostsRecords returns the hosts records for the hostname.
-func (d *DNSFilter) EtcHostsRecords(hostname string) (recs []*hostsfile.Record) {
- if d.conf.EtcHosts != nil {
- return d.conf.EtcHosts.MatchName(hostname)
- }
-
- return recs
-}
-
// SetBlockingMode sets blocking mode properties.
func (d *DNSFilter) SetBlockingMode(mode BlockingMode, bIPv4, bIPv6 netip.Addr) {
d.confMu.Lock()
@@ -628,62 +617,6 @@ func (d *DNSFilter) CheckHost(
return Result{}, nil
}
-// matchSysHosts tries to match the host against the operating system's hosts
-// database. err is always nil.
-func (d *DNSFilter) matchSysHosts(
- host string,
- qtype uint16,
- setts *Settings,
-) (res Result, err error) {
- // TODO(e.burkov): Where else is this checked?
- if !setts.FilteringEnabled || d.conf.EtcHosts == nil {
- return res, nil
- }
-
- var recs []*hostsfile.Record
- switch qtype {
- case dns.TypeA, dns.TypeAAAA:
- recs = d.conf.EtcHosts.MatchName(host)
- case dns.TypePTR:
- var ip net.IP
- ip, err = netutil.IPFromReversedAddr(host)
- if err != nil {
- log.Debug("filtering: failed to parse PTR record %q: %s", host, err)
-
- return res, nil
- }
-
- addr, _ := netip.AddrFromSlice(ip)
- recs = d.conf.EtcHosts.MatchAddr(addr)
- default:
- log.Debug("filtering: unsupported query type %s", dns.Type(qtype))
- }
-
- var vals []rules.RRValue
- var resRules []*ResultRule
- resRulesLen := 0
- for _, rec := range recs {
- vals, resRules = appendRewriteResultFromHost(vals, resRules, rec, qtype)
- if len(resRules) > resRulesLen {
- resRulesLen = len(resRules)
- log.Debug("filtering: matched %s in %q", host, rec.Source)
- }
- }
-
- if len(vals) > 0 {
- res.DNSRewriteResult = &DNSRewriteResult{
- Response: DNSRewriteResultResponse{
- qtype: vals,
- },
- RCode: dns.RcodeSuccess,
- }
- res.Rules = resRules
- res.Reason = RewrittenRule
- }
-
- return res, nil
-}
-
// processRewrites performs filtering based on the legacy rewrite records.
//
// Firstly, it finds CNAME rewrites for host. If the CNAME is the same as host,
diff --git a/internal/filtering/hosts.go b/internal/filtering/hosts.go
new file mode 100644
index 00000000..79cb69ac
--- /dev/null
+++ b/internal/filtering/hosts.go
@@ -0,0 +1,92 @@
+package filtering
+
+import (
+ "fmt"
+ "net/netip"
+
+ "github.com/AdguardTeam/golibs/hostsfile"
+ "github.com/AdguardTeam/golibs/log"
+ "github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/urlfilter/rules"
+ "github.com/miekg/dns"
+)
+
+// matchSysHosts tries to match the host against the operating system's hosts
+// database. err is always nil.
+func (d *DNSFilter) matchSysHosts(
+ host string,
+ qtype uint16,
+ setts *Settings,
+) (res Result, err error) {
+ // TODO(e.burkov): Where else is this checked?
+ if !setts.FilteringEnabled || d.conf.EtcHosts == nil {
+ return Result{}, nil
+ }
+
+ vals, rs, matched := hostsRewrites(qtype, host, d.conf.EtcHosts)
+ if !matched {
+ return Result{}, nil
+ }
+
+ return Result{
+ DNSRewriteResult: &DNSRewriteResult{
+ Response: DNSRewriteResultResponse{
+ qtype: vals,
+ },
+ RCode: dns.RcodeSuccess,
+ },
+ Rules: rs,
+ Reason: RewrittenAutoHosts,
+ }, nil
+}
+
+// hostsRewrites returns values and rules matched by qt and host within hs.
+func hostsRewrites(
+ qtype uint16,
+ host string,
+ hs hostsfile.Storage,
+) (vals []rules.RRValue, rls []*ResultRule, matched bool) {
+ var isValidProto func(netip.Addr) (ok bool)
+ switch qtype {
+ case dns.TypeA:
+ isValidProto = netip.Addr.Is4
+ case dns.TypeAAAA:
+ isValidProto = netip.Addr.Is6
+ case dns.TypePTR:
+ addr, err := netutil.IPFromReversedAddr(host)
+ if err != nil {
+ log.Debug("filtering: failed to parse PTR record %q: %s", host, err)
+
+ return nil, nil, false
+ }
+
+ names := hs.ByAddr(addr)
+
+ for _, name := range names {
+ vals = append(vals, name)
+ rls = append(rls, &ResultRule{
+ Text: fmt.Sprintf("%s %s", addr, name),
+ FilterListID: SysHostsListID,
+ })
+ }
+
+ return vals, rls, len(names) > 0
+ default:
+ log.Debug("filtering: unsupported qtype %d", qtype)
+
+ return nil, nil, false
+ }
+
+ addrs := hs.ByName(host)
+ for _, addr := range addrs {
+ if isValidProto(addr) {
+ vals = append(vals, addr)
+ }
+ rls = append(rls, &ResultRule{
+ Text: fmt.Sprintf("%s %s", addr, host),
+ FilterListID: SysHostsListID,
+ })
+ }
+
+ return vals, rls, len(addrs) > 0
+}
diff --git a/internal/filtering/hosts_test.go b/internal/filtering/hosts_test.go
new file mode 100644
index 00000000..baa6675c
--- /dev/null
+++ b/internal/filtering/hosts_test.go
@@ -0,0 +1,191 @@
+package filtering
+
+import (
+ "fmt"
+ "net/netip"
+ "testing"
+ "testing/fstest"
+
+ "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
+ "github.com/AdguardTeam/AdGuardHome/internal/aghtest"
+ "github.com/AdguardTeam/golibs/testutil"
+ "github.com/AdguardTeam/urlfilter/rules"
+ "github.com/miekg/dns"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
+ addrv4 := netip.MustParseAddr("1.2.3.4")
+ addrv6 := netip.MustParseAddr("::1")
+ addrMapped := netip.MustParseAddr("::ffff:1.2.3.4")
+ addrv4Dup := netip.MustParseAddr("4.3.2.1")
+
+ data := fmt.Sprintf(
+ ""+
+ "%[1]s v4.host.example\n"+
+ "%[2]s v6.host.example\n"+
+ "%[3]s mapped.host.example\n"+
+ "%[4]s v4.host.with-dup\n"+
+ "%[4]s v4.host.with-dup\n",
+ addrv4,
+ addrv6,
+ addrMapped,
+ addrv4Dup,
+ )
+
+ files := fstest.MapFS{
+ "hosts": &fstest.MapFile{
+ Data: []byte(data),
+ },
+ }
+ watcher := &aghtest.FSWatcher{
+ OnEvents: func() (e <-chan struct{}) { return nil },
+ OnAdd: func(name string) (err error) { return nil },
+ OnClose: func() (err error) { return nil },
+ }
+ hc, err := aghnet.NewHostsContainer(files, watcher, "hosts")
+ require.NoError(t, err)
+ testutil.CleanupAndRequireSuccess(t, hc.Close)
+
+ conf := &Config{
+ EtcHosts: hc,
+ }
+ f, err := New(conf, nil)
+ require.NoError(t, err)
+
+ setts := &Settings{
+ FilteringEnabled: true,
+ }
+
+ testCases := []struct {
+ name string
+ host string
+ wantRules []*ResultRule
+ wantResps []rules.RRValue
+ dtyp uint16
+ }{{
+ name: "v4",
+ host: "v4.host.example",
+ dtyp: dns.TypeA,
+ wantRules: []*ResultRule{{
+ Text: "1.2.3.4 v4.host.example",
+ FilterListID: SysHostsListID,
+ }},
+ wantResps: []rules.RRValue{addrv4},
+ }, {
+ name: "v6",
+ host: "v6.host.example",
+ dtyp: dns.TypeAAAA,
+ wantRules: []*ResultRule{{
+ Text: "::1 v6.host.example",
+ FilterListID: SysHostsListID,
+ }},
+ wantResps: []rules.RRValue{addrv6},
+ }, {
+ name: "mapped",
+ host: "mapped.host.example",
+ dtyp: dns.TypeAAAA,
+ wantRules: []*ResultRule{{
+ Text: "::ffff:1.2.3.4 mapped.host.example",
+ FilterListID: SysHostsListID,
+ }},
+ wantResps: []rules.RRValue{addrMapped},
+ }, {
+ name: "ptr",
+ host: "4.3.2.1.in-addr.arpa",
+ dtyp: dns.TypePTR,
+ wantRules: []*ResultRule{{
+ Text: "1.2.3.4 v4.host.example",
+ FilterListID: SysHostsListID,
+ }},
+ wantResps: []rules.RRValue{"v4.host.example"},
+ }, {
+ name: "ptr-mapped",
+ host: "4.0.3.0.2.0.1.0.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
+ dtyp: dns.TypePTR,
+ wantRules: []*ResultRule{{
+ Text: "::ffff:1.2.3.4 mapped.host.example",
+ FilterListID: SysHostsListID,
+ }},
+ wantResps: []rules.RRValue{"mapped.host.example"},
+ }, {
+ name: "not_found_v4",
+ host: "non.existent.example",
+ dtyp: dns.TypeA,
+ wantRules: nil,
+ wantResps: nil,
+ }, {
+ name: "not_found_v6",
+ host: "non.existent.example",
+ dtyp: dns.TypeAAAA,
+ wantRules: nil,
+ wantResps: nil,
+ }, {
+ name: "not_found_ptr",
+ host: "4.3.2.2.in-addr.arpa",
+ dtyp: dns.TypePTR,
+ wantRules: nil,
+ wantResps: nil,
+ }, {
+ name: "v4_mismatch",
+ host: "v4.host.example",
+ dtyp: dns.TypeAAAA,
+ wantRules: []*ResultRule{{
+ Text: fmt.Sprintf("%s v4.host.example", addrv4),
+ FilterListID: SysHostsListID,
+ }},
+ wantResps: nil,
+ }, {
+ name: "v6_mismatch",
+ host: "v6.host.example",
+ dtyp: dns.TypeA,
+ wantRules: []*ResultRule{{
+ Text: fmt.Sprintf("%s v6.host.example", addrv6),
+ FilterListID: SysHostsListID,
+ }},
+ wantResps: nil,
+ }, {
+ name: "wrong_ptr",
+ host: "4.3.2.1.ip6.arpa",
+ dtyp: dns.TypePTR,
+ wantRules: nil,
+ wantResps: nil,
+ }, {
+ name: "unsupported_type",
+ host: "v4.host.example",
+ dtyp: dns.TypeCNAME,
+ wantRules: nil,
+ wantResps: nil,
+ }, {
+ name: "v4_dup",
+ host: "v4.host.with-dup",
+ dtyp: dns.TypeA,
+ wantRules: []*ResultRule{{
+ Text: "4.3.2.1 v4.host.with-dup",
+ FilterListID: SysHostsListID,
+ }},
+ wantResps: []rules.RRValue{addrv4Dup},
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ var res Result
+ res, err = f.CheckHost(tc.host, tc.dtyp, setts)
+ require.NoError(t, err)
+
+ if len(tc.wantRules) == 0 {
+ assert.Empty(t, res.Rules)
+ assert.Nil(t, res.DNSRewriteResult)
+
+ return
+ }
+
+ require.NotNil(t, res.DNSRewriteResult)
+ require.Contains(t, res.DNSRewriteResult.Response, tc.dtyp)
+
+ assert.Equal(t, tc.wantResps, res.DNSRewriteResult.Response[tc.dtyp])
+ assert.Equal(t, tc.wantRules, res.Rules)
+ })
+ }
+}
diff --git a/internal/filtering/http.go b/internal/filtering/http.go
index ca6b8cf9..362ef0e4 100644
--- a/internal/filtering/http.go
+++ b/internal/filtering/http.go
@@ -24,23 +24,25 @@ func validateFilterURL(urlStr string) (err error) {
if filepath.IsAbs(urlStr) {
_, err = os.Stat(urlStr)
- if err != nil {
- // Don't wrap the error since it's informative enough as is.
- return err
- }
- return nil
+ // Don't wrap the error since it's informative enough as is.
+ return err
}
u, err := url.ParseRequestURI(urlStr)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
- } else if s := u.Scheme; s != aghhttp.SchemeHTTP && s != aghhttp.SchemeHTTPS {
+ }
+
+ if s := u.Scheme; s != aghhttp.SchemeHTTP && s != aghhttp.SchemeHTTPS {
return &url.Error{
Op: "Check scheme",
URL: urlStr,
- Err: fmt.Errorf("only %v allowed", []string{aghhttp.SchemeHTTP, aghhttp.SchemeHTTPS}),
+ Err: fmt.Errorf("only %v allowed", []string{
+ aghhttp.SchemeHTTP,
+ aghhttp.SchemeHTTPS,
+ }),
}
}
diff --git a/internal/filtering/rulelist/filter.go b/internal/filtering/rulelist/filter.go
new file mode 100644
index 00000000..278eef5c
--- /dev/null
+++ b/internal/filtering/rulelist/filter.go
@@ -0,0 +1,338 @@
+package rulelist
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardHome/internal/aghrenameio"
+ "github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/ioutil"
+ "github.com/AdguardTeam/golibs/log"
+ "github.com/AdguardTeam/urlfilter/filterlist"
+ "github.com/c2h5oh/datasize"
+)
+
+// Filter contains information about a single rule-list filter.
+//
+// TODO(a.garipov): Use.
+type Filter struct {
+ // url is the URL of this rule list. Supported schemes are:
+ // - http
+ // - https
+ // - file
+ url *url.URL
+
+ // ruleList is the last successfully compiled [filterlist.RuleList].
+ ruleList filterlist.RuleList
+
+ // updated is the time of the last successful update.
+ updated time.Time
+
+ // name is the human-readable name of this rule-list filter.
+ name string
+
+ // uid is the unique ID of this rule-list filter.
+ uid UID
+
+ // urlFilterID is used for working with package urlfilter.
+ urlFilterID URLFilterID
+
+ // rulesCount contains the number of rules in this rule-list filter.
+ rulesCount int
+
+ // checksum is a CRC32 hash used to quickly check if the rules within a list
+ // file have changed.
+ checksum uint32
+
+ // enabled, if true, means that this rule-list filter is used for filtering.
+ //
+ // TODO(a.garipov): Take into account.
+ enabled bool
+}
+
+// FilterConfig contains the configuration for a [Filter].
+type FilterConfig struct {
+ // URL is the URL of this rule-list filter. Supported schemes are:
+ // - http
+ // - https
+ // - file
+ URL *url.URL
+
+ // Name is the human-readable name of this rule-list filter. If not set, it
+ // is either taken from the rule-list data or generated synthetically from
+ // the UID.
+ Name string
+
+ // UID is the unique ID of this rule-list filter.
+ UID UID
+
+ // URLFilterID is used for working with package urlfilter.
+ URLFilterID URLFilterID
+
+ // Enabled, if true, means that this rule-list filter is used for filtering.
+ Enabled bool
+}
+
+// NewFilter creates a new rule-list filter. The filter is not refreshed, so a
+// refresh should be performed before use.
+func NewFilter(c *FilterConfig) (f *Filter, err error) {
+ if c.URL == nil {
+ return nil, errors.Error("no url")
+ }
+
+ switch s := c.URL.Scheme; s {
+ case "http", "https", "file":
+ // Go on.
+ default:
+ return nil, fmt.Errorf("bad url scheme: %q", s)
+ }
+
+ return &Filter{
+ url: c.URL,
+ name: c.Name,
+ uid: c.UID,
+ urlFilterID: c.URLFilterID,
+ enabled: c.Enabled,
+ }, nil
+}
+
+// Refresh updates the data in the rule-list filter. parseBuf is the initial
+// buffer used to parse information from the data. cli and maxSize are only
+// used when f is a URL-based list.
+func (f *Filter) Refresh(
+ ctx context.Context,
+ parseBuf []byte,
+ cli *http.Client,
+ cacheDir string,
+ maxSize datasize.ByteSize,
+) (parseRes *ParseResult, err error) {
+ cachePath := filepath.Join(cacheDir, f.uid.String()+".txt")
+
+ switch s := f.url.Scheme; s {
+ case "http", "https":
+ parseRes, err = f.setFromHTTP(ctx, parseBuf, cli, cachePath, maxSize.Bytes())
+ case "file":
+ parseRes, err = f.setFromFile(parseBuf, f.url.Path, cachePath)
+ default:
+ // Since the URL has been prevalidated in New, consider this a
+ // programmer error.
+ panic(fmt.Errorf("bad url scheme: %q", s))
+ }
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return nil, err
+ }
+
+ if f.checksum != parseRes.Checksum {
+ f.checksum = parseRes.Checksum
+ f.rulesCount = parseRes.RulesCount
+ f.setName(parseRes.Title)
+ f.updated = time.Now()
+ }
+
+ return parseRes, nil
+}
+
+// setFromHTTP sets the rule-list filter's data from its URL. It also caches
+// the data into a file.
+func (f *Filter) setFromHTTP(
+ ctx context.Context,
+ parseBuf []byte,
+ cli *http.Client,
+ cachePath string,
+ maxSize uint64,
+) (parseRes *ParseResult, err error) {
+ defer func() { err = errors.Annotate(err, "setting from http: %w") }()
+
+ text, parseRes, err := f.readFromHTTP(ctx, parseBuf, cli, cachePath, maxSize)
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return nil, err
+ }
+
+ // TODO(a.garipov): Add filterlist.BytesRuleList.
+ f.ruleList = &filterlist.StringRuleList{
+ ID: f.urlFilterID,
+ RulesText: text,
+ IgnoreCosmetic: true,
+ }
+
+ return parseRes, nil
+}
+
+// readFromHTTP reads the data from the rule-list filter's URL into the cache
+// file as well as returns it as a string. The data is filtered through a
+// parser and so is free from comments, unnecessary whitespace, etc.
+func (f *Filter) readFromHTTP(
+ ctx context.Context,
+ parseBuf []byte,
+ cli *http.Client,
+ cachePath string,
+ maxSize uint64,
+) (text string, parseRes *ParseResult, err error) {
+ urlStr := f.url.String()
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
+ if err != nil {
+ return "", nil, fmt.Errorf("making request for http url %q: %w", urlStr, err)
+ }
+
+ resp, err := cli.Do(req)
+ if err != nil {
+ return "", nil, fmt.Errorf("requesting from http url: %w", err)
+ }
+ defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
+
+ // TODO(a.garipov): Use [agdhttp.CheckStatus] when it's moved to golibs.
+ if resp.StatusCode != http.StatusOK {
+ return "", nil, fmt.Errorf("got status code %d, want %d", resp.StatusCode, http.StatusOK)
+ }
+
+ fltFile, err := aghrenameio.NewPendingFile(cachePath, 0o644)
+ if err != nil {
+ return "", nil, fmt.Errorf("creating temp file: %w", err)
+ }
+ defer func() { err = aghrenameio.WithDeferredCleanup(err, fltFile) }()
+
+ buf := &bytes.Buffer{}
+ mw := io.MultiWriter(buf, fltFile)
+
+ parser := NewParser()
+ httpBody := ioutil.LimitReader(resp.Body, maxSize)
+ parseRes, err = parser.Parse(mw, httpBody, parseBuf)
+ if err != nil {
+ return "", nil, fmt.Errorf("parsing response from http url %q: %w", urlStr, err)
+ }
+
+ return buf.String(), parseRes, nil
+}
+
+// setName sets the title using either the already-present name, the given title
+// from the rule-list data, or a synthetic name.
+func (f *Filter) setName(title string) {
+ if f.name != "" {
+ return
+ }
+
+ if title != "" {
+ f.name = title
+
+ return
+ }
+
+ f.name = fmt.Sprintf("List %s", f.uid)
+}
+
+// setFromFile sets the rule-list filter's data from a file path. It also
+// caches the data into a file.
+//
+// TODO(a.garipov): Retest on Windows once rule-list updater is committed. See
+// if calling Close is necessary here.
+func (f *Filter) setFromFile(
+ parseBuf []byte,
+ filePath string,
+ cachePath string,
+) (parseRes *ParseResult, err error) {
+ defer func() { err = errors.Annotate(err, "setting from file: %w") }()
+
+ parseRes, err = parseIntoCache(parseBuf, filePath, cachePath)
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return nil, err
+ }
+
+ err = f.Close()
+ if err != nil {
+ return nil, fmt.Errorf("closing old rule list: %w", err)
+ }
+
+ rl, err := filterlist.NewFileRuleList(f.urlFilterID, cachePath, true)
+ if err != nil {
+ return nil, fmt.Errorf("opening new rule list: %w", err)
+ }
+
+ f.ruleList = rl
+
+ return parseRes, nil
+}
+
+// parseIntoCache copies the relevant the data from filePath into cachePath
+// while also parsing it.
+func parseIntoCache(
+ parseBuf []byte,
+ filePath string,
+ cachePath string,
+) (parseRes *ParseResult, err error) {
+ tmpFile, err := aghrenameio.NewPendingFile(cachePath, 0o644)
+ if err != nil {
+ return nil, fmt.Errorf("creating temp file: %w", err)
+ }
+ defer func() { err = aghrenameio.WithDeferredCleanup(err, tmpFile) }()
+
+ // #nosec G304 -- Assume that cachePath is always cacheDir joined with a
+ // uid using [filepath.Join].
+ f, err := os.Open(filePath)
+ if err != nil {
+ return nil, fmt.Errorf("opening src file: %w", err)
+ }
+ defer func() { err = errors.WithDeferred(err, f.Close()) }()
+
+ parser := NewParser()
+ parseRes, err = parser.Parse(tmpFile, f, parseBuf)
+ if err != nil {
+ return nil, fmt.Errorf("copying src file: %w", err)
+ }
+
+ return parseRes, nil
+}
+
+// Close closes the underlying rule list.
+func (f *Filter) Close() (err error) {
+ if f.ruleList == nil {
+ return nil
+ }
+
+ return f.ruleList.Close()
+}
+
+// filterUpdate represents a single ongoing rule-list filter update.
+//
+//lint:ignore U1000 TODO(a.garipov): Use.
+type filterUpdate struct {
+ httpCli *http.Client
+ cacheDir string
+ name string
+ parseBuf []byte
+ maxSize datasize.ByteSize
+}
+
+// process runs an update of a single rule-list.
+func (u *filterUpdate) process(ctx context.Context, f *Filter) (err error) {
+ prevChecksum := f.checksum
+ parseRes, err := f.Refresh(ctx, u.parseBuf, u.httpCli, u.cacheDir, u.maxSize)
+ if err != nil {
+ return fmt.Errorf("updating %s: %w", f.uid, err)
+ }
+
+ if prevChecksum == parseRes.Checksum {
+ log.Info("filtering: filter %q: filter %q: no change", u.name, f.uid)
+
+ return nil
+ }
+
+ log.Info(
+ "filtering: updated filter %q: filter %q: %d bytes, %d rules",
+ u.name,
+ f.uid,
+ parseRes.BytesWritten,
+ parseRes.RulesCount,
+ )
+
+ return nil
+}
diff --git a/internal/filtering/rulelist/filter_test.go b/internal/filtering/rulelist/filter_test.go
new file mode 100644
index 00000000..93cd6e9c
--- /dev/null
+++ b/internal/filtering/rulelist/filter_test.go
@@ -0,0 +1,107 @@
+package rulelist_test
+
+import (
+ "context"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
+ "github.com/AdguardTeam/golibs/testutil"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestFilter_Refresh(t *testing.T) {
+ cacheDir := t.TempDir()
+ uid := rulelist.MustNewUID()
+
+ initialFile := filepath.Join(cacheDir, "initial.txt")
+ initialData := []byte(
+ testRuleTextTitle +
+ testRuleTextBlocked,
+ )
+ writeErr := os.WriteFile(initialFile, initialData, 0o644)
+ require.NoError(t, writeErr)
+
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ pt := testutil.PanicT{}
+
+ _, err := io.WriteString(w, testRuleTextTitle+testRuleTextBlocked)
+ require.NoError(pt, err)
+ }))
+
+ srvURL, urlErr := url.Parse(srv.URL)
+ require.NoError(t, urlErr)
+
+ testCases := []struct {
+ url *url.URL
+ name string
+ wantNewErrMsg string
+ }{{
+ url: nil,
+ name: "nil_url",
+ wantNewErrMsg: "no url",
+ }, {
+ url: &url.URL{
+ Scheme: "ftp",
+ },
+ name: "bad_scheme",
+ wantNewErrMsg: `bad url scheme: "ftp"`,
+ }, {
+ name: "file",
+ url: &url.URL{
+ Scheme: "file",
+ Path: initialFile,
+ },
+ wantNewErrMsg: "",
+ }, {
+ name: "http",
+ url: srvURL,
+ wantNewErrMsg: "",
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ f, err := rulelist.NewFilter(&rulelist.FilterConfig{
+ URL: tc.url,
+ Name: tc.name,
+ UID: uid,
+ URLFilterID: testURLFilterID,
+ Enabled: true,
+ })
+ if tc.wantNewErrMsg != "" {
+ assert.EqualError(t, err, tc.wantNewErrMsg)
+
+ return
+ }
+
+ testutil.CleanupAndRequireSuccess(t, f.Close)
+
+ require.NotNil(t, f)
+
+ ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+ t.Cleanup(cancel)
+
+ buf := make([]byte, rulelist.DefaultRuleBufSize)
+ cli := &http.Client{
+ Timeout: testTimeout,
+ }
+
+ res, err := f.Refresh(ctx, buf, cli, cacheDir, rulelist.DefaultMaxRuleListSize)
+ require.NoError(t, err)
+
+ assert.Equal(t, testTitle, res.Title)
+ assert.Equal(t, len(testRuleTextBlocked), res.BytesWritten)
+ assert.Equal(t, 1, res.RulesCount)
+
+ // Check that the cached file exists.
+ _, err = os.Stat(filepath.Join(cacheDir, uid.String()+".txt"))
+ require.NoError(t, err)
+ })
+ }
+}
diff --git a/internal/filtering/rulelist/parser_test.go b/internal/filtering/rulelist/parser_test.go
index 5554458d..45a8e465 100644
--- a/internal/filtering/rulelist/parser_test.go
+++ b/internal/filtering/rulelist/parser_test.go
@@ -69,12 +69,12 @@ func TestParser_Parse(t *testing.T) {
wantWritten: len(testRuleTextBlocked) + len(testRuleTextHTML),
}, {
name: "title",
- in: "! Title: Test Title \n" +
+ in: testRuleTextTitle +
"! Title: Bad, Ignored Title\n" +
testRuleTextBlocked,
wantDst: testRuleTextBlocked,
wantErrMsg: "",
- wantTitle: "Test Title",
+ wantTitle: testTitle,
wantRulesNum: 1,
wantWritten: len(testRuleTextBlocked),
}, {
@@ -87,14 +87,14 @@ func TestParser_Parse(t *testing.T) {
wantWritten: len(testRuleTextCosmetic),
}, {
name: "bad_char",
- in: "! Title: Test Title \n" +
+ in: testRuleTextTitle +
testRuleTextBlocked +
">>>\x7F<<<",
wantDst: testRuleTextBlocked,
wantErrMsg: "line 3: " +
"character 4: " +
"likely binary character '\\x7f'",
- wantTitle: "Test Title",
+ wantTitle: testTitle,
wantRulesNum: 1,
wantWritten: len(testRuleTextBlocked),
}, {
diff --git a/internal/filtering/rulelist/rulelist.go b/internal/filtering/rulelist/rulelist.go
index 464650a1..e0fd61b4 100644
--- a/internal/filtering/rulelist/rulelist.go
+++ b/internal/filtering/rulelist/rulelist.go
@@ -1,9 +1,55 @@
// Package rulelist contains the implementation of the standard rule-list
// filter that wraps an urlfilter filtering-engine.
//
-// TODO(a.garipov): Expand.
+// TODO(a.garipov): Add a new update worker.
package rulelist
+import (
+ "fmt"
+
+ "github.com/c2h5oh/datasize"
+ "github.com/google/uuid"
+)
+
// DefaultRuleBufSize is the default length of a buffer used to read a line with
// a filtering rule, in bytes.
+//
+// TODO(a.garipov): Consider using [datasize.ByteSize]. It is currently only
+// used as an int.
const DefaultRuleBufSize = 1024
+
+// DefaultMaxRuleListSize is the default maximum filtering-rule list size.
+const DefaultMaxRuleListSize = 64 * datasize.MB
+
+// URLFilterID is a semantic type-alias for IDs used for working with package
+// urlfilter.
+type URLFilterID = int
+
+// UID is the type for the unique IDs of filtering-rule lists.
+type UID uuid.UUID
+
+// NewUID returns a new filtering-rule list UID. Any error returned is an error
+// from the cryptographic randomness reader.
+func NewUID() (uid UID, err error) {
+ uuidv7, err := uuid.NewV7()
+
+ return UID(uuidv7), err
+}
+
+// MustNewUID is a wrapper around [NewUID] that panics if there is an error.
+func MustNewUID() (uid UID) {
+ uid, err := NewUID()
+ if err != nil {
+ panic(fmt.Errorf("unexpected uuidv7 error: %w", err))
+ }
+
+ return uid
+}
+
+// type check
+var _ fmt.Stringer = UID{}
+
+// String implements the [fmt.Stringer] interface for UID.
+func (id UID) String() (s string) {
+ return uuid.UUID(id).String()
+}
diff --git a/internal/filtering/rulelist/rulelist_test.go b/internal/filtering/rulelist/rulelist_test.go
index aec6f33b..dc79d503 100644
--- a/internal/filtering/rulelist/rulelist_test.go
+++ b/internal/filtering/rulelist/rulelist_test.go
@@ -1,16 +1,34 @@
package rulelist_test
-import "time"
+import (
+ "testing"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
+ "github.com/AdguardTeam/golibs/testutil"
+)
+
+func TestMain(m *testing.M) {
+ testutil.DiscardLogOutput(m)
+}
// testTimeout is the common timeout for tests.
const testTimeout = 1 * time.Second
-// Common texts for tests.
+// testURLFilterID is the common [rulelist.URLFilterID] for tests.
+const testURLFilterID rulelist.URLFilterID = 1
+
+// testTitle is the common title for tests.
+const testTitle = "Test Title"
+
+// Common rule texts for tests.
const (
testRuleTextBadTab = "||bad-tab-and-comment.example^\t# A comment.\n"
testRuleTextBlocked = "||blocked.example^\n"
+ testRuleTextBlocked2 = "||blocked-2.example^\n"
testRuleTextEtcHostsTab = "0.0.0.0 tab..example^\t# A comment.\n"
testRuleTextHTML = "\n"
+ testRuleTextTitle = "! Title: " + testTitle + " \n"
// testRuleTextCosmetic is a cosmetic rule with a zero-width non-joiner.
//
diff --git a/internal/filtering/safesearch/safesearch.go b/internal/filtering/safesearch/safesearch.go
index f2d2b70c..47e66ac6 100644
--- a/internal/filtering/safesearch/safesearch.go
+++ b/internal/filtering/safesearch/safesearch.go
@@ -226,7 +226,8 @@ func (ss *Default) searchHost(host string, qtype rules.RRType) (res *rules.DNSRe
// empty result is converted into a NODATA response.
//
// TODO(a.garipov): Use the main rewrite result mechanism used in
-// [dnsforward.Server.filterDNSRequest].
+// [dnsforward.Server.filterDNSRequest]. Now we resolve IPs for CNAME to save
+// them in the safe search cache.
func (ss *Default) newResult(
rewrite *rules.DNSRewrite,
qtype rules.RRType,
@@ -255,6 +256,8 @@ func (ss *Default) newResult(
return res, nil
}
+ res.CanonName = host
+
ss.log(log.DEBUG, "resolving %q", host)
ips, err := ss.resolver.LookupIP(context.Background(), qtypeToProto(qtype), host)
diff --git a/internal/filtering/servicelist.go b/internal/filtering/servicelist.go
index ec893ecf..f691104e 100644
--- a/internal/filtering/servicelist.go
+++ b/internal/filtering/servicelist.go
@@ -12,6 +12,15 @@ type blockedService struct {
// blockedServices contains raw blocked service data.
var blockedServices = []blockedService{{
+ ID: "4chan",
+ Name: "4chan",
+ IconSVG: []byte("
"),
+ Rules: []string{
+ "||4cdn.org^",
+ "||4chan.org^",
+ "||4channel.org^",
+ },
+}, {
ID: "500px",
Name: "500px",
IconSVG: []byte("
"),
@@ -256,6 +265,41 @@ var blockedServices = []blockedService{{
"||z.cn^",
"||zappos^",
},
+}, {
+ ID: "amazon_streaming",
+ Name: "Amazon Streaming",
+ IconSVG: []byte("
"),
+ Rules: []string{
+ "||aiv-delivery.net^",
+ "||amazonmusic.com^",
+ "||amazonprimevideo.cn^",
+ "||amazonprimevideo.com.cn^",
+ "||amazonprimevideos.com^",
+ "||amazonvideo.cc^",
+ "||amazonvideo.com^",
+ "||amazonvideodirect.com^",
+ "||atv-ext-eu.amazon.com^",
+ "||atv-ext-fe.amazon.com^",
+ "||atv-ext.amazon.com^",
+ "||atv-ps-eu.amazon.co.uk^",
+ "||atv-ps-eu.amazon.com^",
+ "||atv-ps-fe.amazon.co.jp^",
+ "||atv-ps-fe.amazon.com^",
+ "||atv-ps.amazon.com^",
+ "||av-eu.amazon.com^",
+ "||av-na.amazon.com^",
+ "||music.a2z.com^",
+ "||music.amazon.co.uk^",
+ "||music.amazon.com^",
+ "||music.amazon.in^",
+ "||prime-video.com^",
+ "||primevideo.cc^",
+ "||primevideo.com^",
+ "||primevideo.info^",
+ "||primevideo.org^",
+ "||primevideo.tv^",
+ "||video.a2z.com^",
+ },
}, {
ID: "amino",
Name: "Amino",
@@ -375,6 +419,7 @@ var blockedServices = []blockedService{{
"||biliapi.com^",
"||biliapi.net^",
"||bilibili.cc^",
+ "||bilibili.cn^",
"||bilibili.com^",
"||bilibili.net^",
"||bilibili.tv^",
@@ -392,6 +437,8 @@ var blockedServices = []blockedService{{
"||biligame.com^",
"||biligame.net^",
"||biligo.com^",
+ "||biliimg.com^",
+ "||biliintl.com^",
"||bilivideo.cn^",
"||bilivideo.com^",
"||bilivideo.net^",
@@ -583,6 +630,14 @@ var blockedServices = []blockedService{{
"||discordstatus.com^",
"||watchanimeattheoffice.com^",
},
+}, {
+ ID: "discoveryplus",
+ Name: "Discovery+",
+ IconSVG: []byte("
"),
+ Rules: []string{
+ "||disco-api.com^",
+ "||discoveryplus.com^",
+ },
}, {
ID: "disneyplus",
Name: "Disney+",
@@ -1449,6 +1504,16 @@ var blockedServices = []blockedService{{
"||flickrpro.com^",
"||staticflickr.com^",
},
+}, {
+ ID: "globoplay",
+ Name: "Globoplay",
+ IconSVG: []byte("
"),
+ Rules: []string{
+ "||cloud-jarvis.globo.com^",
+ "||globoplay.com.br^",
+ "||globoplay.com^",
+ "||globoplay.globo.com^",
+ },
}, {
ID: "gog",
Name: "GOG",
@@ -1475,6 +1540,7 @@ var blockedServices = []blockedService{{
"||hbomax.com^",
"||hbomaxcdn.com^",
"||hbonow.com^",
+ "||max.com^",
"||maxgo.com^",
},
}, {
@@ -1602,6 +1668,12 @@ var blockedServices = []blockedService{{
Rules: []string{
"||iq.com^",
"||iqiyi.com^",
+ "||iqiyipic.com^",
+ "||pps.tv^",
+ "||ppsimg.com^",
+ "||qiyi.com^",
+ "||qiyipic.com^",
+ "||qy.net^",
},
}, {
ID: "kakaotalk",
@@ -2140,6 +2212,16 @@ var blockedServices = []blockedService{{
"||rockstargames.com^",
"||rsg.sc^",
},
+}, {
+ ID: "samsung_tv_plus",
+ Name: "Samsung TV Plus",
+ IconSVG: []byte("
"),
+ Rules: []string{
+ "||internetat.tv^",
+ "||samsung.wurl.tv^",
+ "||samsungcloud.tv^",
+ "||samsungtvplus.com^",
+ },
}, {
ID: "shein",
Name: "Shein",
@@ -2328,7 +2410,6 @@ var blockedServices = []blockedService{{
Name: "TikTok",
IconSVG: []byte("
"),
Rules: []string{
- "|p16-tiktokcdn-com.akamaized.net^",
"||amemv.com^",
"||bdurl.com^",
"||bytecdn.cn^",
@@ -2348,6 +2429,7 @@ var blockedServices = []blockedService{{
"||muscdn.com^",
"||musical.ly^",
"||p16-tiktok-*.ibyteimg.com^",
+ "||p16-tiktokcdn-com.akamaized.net^",
"||pstatp.com^",
"||snssdk.com^",
"||tiktok.com^",
@@ -2356,6 +2438,7 @@ var blockedServices = []blockedService{{
"||tiktokv.com^",
"||ttlivecdn.com.c.bytefcdn-oversea.com^",
"||ttlivecdn.com^",
+ "||v*.tiktokcdn-eu.com^",
},
}, {
ID: "tinder",
@@ -2580,6 +2663,7 @@ var blockedServices = []blockedService{{
IconSVG: []byte("
"),
Rules: []string{
"||xhscdn.com^",
+ "||xhscdn.net^",
"||xiaohongshu.com^",
},
}, {
diff --git a/internal/home/client.go b/internal/home/client.go
index 70ce112e..64e9b677 100644
--- a/internal/home/client.go
+++ b/internal/home/client.go
@@ -1,19 +1,53 @@
package home
import (
+ "encoding"
"fmt"
+ "net"
+ "net/netip"
+ "strings"
"time"
- "github.com/AdguardTeam/AdGuardHome/internal/client"
+ "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
- "github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/dnsproxy/proxy"
+ "github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
+ "github.com/google/uuid"
+ "golang.org/x/exp/slices"
)
-// Client contains information about persistent clients.
-type Client struct {
+// UID is the type for the unique IDs of persistent clients.
+type UID uuid.UUID
+
+// NewUID returns a new persistent client UID. Any error returned is an error
+// from the cryptographic randomness reader.
+func NewUID() (uid UID, err error) {
+ uuidv7, err := uuid.NewV7()
+
+ return UID(uuidv7), err
+}
+
+// type check
+var _ encoding.TextMarshaler = UID{}
+
+// MarshalText implements the [encoding.TextMarshaler] for UID.
+func (uid UID) MarshalText() ([]byte, error) {
+ return uuid.UUID(uid).MarshalText()
+}
+
+// type check
+var _ encoding.TextUnmarshaler = (*UID)(nil)
+
+// UnmarshalText implements the [encoding.TextUnmarshaler] interface for UID.
+func (uid *UID) UnmarshalText(data []byte) error {
+ return (*uuid.UUID)(uid).UnmarshalText(data)
+}
+
+// persistentClient contains information about persistent clients.
+type persistentClient struct {
// upstreamConfig is the custom upstream configuration for this client. If
// it's nil, it has not been initialized yet. If it's non-nil and empty,
// there are no valid upstreams. If it's non-nil and non-empty, these
@@ -29,10 +63,18 @@ type Client struct {
Name string
- IDs []string
Tags []string
Upstreams []string
+ IPs []netip.Addr
+ // TODO(s.chzhen): Use netutil.Prefix.
+ Subnets []netip.Prefix
+ MACs []net.HardwareAddr
+ ClientIDs []string
+
+ // UID is the unique identifier of the persistent client.
+ UID UID
+
UpstreamsCacheSize uint32
UpstreamsCacheEnabled bool
@@ -45,21 +87,153 @@ type Client struct {
IgnoreStatistics bool
}
-// ShallowClone returns a deep copy of the client, except upstreamConfig,
+// setTags sets the tags if they are known, otherwise logs an unknown tag.
+func (c *persistentClient) setTags(tags []string, known *stringutil.Set) {
+ for _, t := range tags {
+ if !known.Has(t) {
+ log.Info("skipping unknown tag %q", t)
+
+ continue
+ }
+
+ c.Tags = append(c.Tags, t)
+ }
+
+ slices.Sort(c.Tags)
+}
+
+// setIDs parses a list of strings into typed fields and returns an error if
+// there is one.
+func (c *persistentClient) setIDs(ids []string) (err error) {
+ for _, id := range ids {
+ err = c.setID(id)
+ if err != nil {
+ return err
+ }
+ }
+
+ slices.SortFunc(c.IPs, netip.Addr.Compare)
+
+ // TODO(s.chzhen): Use netip.PrefixCompare in Go 1.23.
+ slices.SortFunc(c.Subnets, subnetCompare)
+ slices.SortFunc(c.MACs, slices.Compare[net.HardwareAddr])
+ slices.Sort(c.ClientIDs)
+
+ return nil
+}
+
+// subnetCompare is a comparison function for the two subnets. It returns -1 if
+// x sorts before y, 1 if x sorts after y, and 0 if their relative sorting
+// position is the same.
+func subnetCompare(x, y netip.Prefix) (cmp int) {
+ if x == y {
+ return 0
+ }
+
+ xAddr, xBits := x.Addr(), x.Bits()
+ yAddr, yBits := y.Addr(), y.Bits()
+ if xBits == yBits {
+ return xAddr.Compare(yAddr)
+ }
+
+ if xBits > yBits {
+ return -1
+ } else {
+ return 1
+ }
+}
+
+// setID parses id into typed field if there is no error.
+func (c *persistentClient) setID(id string) (err error) {
+ if id == "" {
+ return errors.Error("clientid is empty")
+ }
+
+ var ip netip.Addr
+ if ip, err = netip.ParseAddr(id); err == nil {
+ c.IPs = append(c.IPs, ip)
+
+ return nil
+ }
+
+ var subnet netip.Prefix
+ if subnet, err = netip.ParsePrefix(id); err == nil {
+ c.Subnets = append(c.Subnets, subnet)
+
+ return nil
+ }
+
+ var mac net.HardwareAddr
+ if mac, err = net.ParseMAC(id); err == nil {
+ c.MACs = append(c.MACs, mac)
+
+ return nil
+ }
+
+ err = dnsforward.ValidateClientID(id)
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return err
+ }
+
+ c.ClientIDs = append(c.ClientIDs, strings.ToLower(id))
+
+ return nil
+}
+
+// ids returns a list of client ids containing at least one element.
+func (c *persistentClient) ids() (ids []string) {
+ ids = make([]string, 0, c.idsLen())
+
+ for _, ip := range c.IPs {
+ ids = append(ids, ip.String())
+ }
+
+ for _, subnet := range c.Subnets {
+ ids = append(ids, subnet.String())
+ }
+
+ for _, mac := range c.MACs {
+ ids = append(ids, mac.String())
+ }
+
+ return append(ids, c.ClientIDs...)
+}
+
+// idsLen returns a length of client ids.
+func (c *persistentClient) idsLen() (n int) {
+ return len(c.IPs) + len(c.Subnets) + len(c.MACs) + len(c.ClientIDs)
+}
+
+// equalIDs returns true if the ids of the current and previous clients are the
+// same.
+func (c *persistentClient) equalIDs(prev *persistentClient) (equal bool) {
+ return slices.Equal(c.IPs, prev.IPs) &&
+ slices.Equal(c.Subnets, prev.Subnets) &&
+ slices.EqualFunc(c.MACs, prev.MACs, slices.Equal[net.HardwareAddr]) &&
+ slices.Equal(c.ClientIDs, prev.ClientIDs)
+}
+
+// shallowClone returns a deep copy of the client, except upstreamConfig,
// safeSearchConf, SafeSearch fields, because it's difficult to copy them.
-func (c *Client) ShallowClone() (sh *Client) {
- clone := *c
+func (c *persistentClient) shallowClone() (clone *persistentClient) {
+ clone = &persistentClient{}
+ *clone = *c
clone.BlockedServices = c.BlockedServices.Clone()
- clone.IDs = stringutil.CloneSlice(c.IDs)
- clone.Tags = stringutil.CloneSlice(c.Tags)
- clone.Upstreams = stringutil.CloneSlice(c.Upstreams)
+ clone.Tags = slices.Clone(c.Tags)
+ clone.Upstreams = slices.Clone(c.Upstreams)
- return &clone
+ clone.IPs = slices.Clone(c.IPs)
+ clone.Subnets = slices.Clone(c.Subnets)
+ clone.MACs = slices.Clone(c.MACs)
+ clone.ClientIDs = slices.Clone(c.ClientIDs)
+
+ return clone
}
// closeUpstreams closes the client-specific upstream config of c if any.
-func (c *Client) closeUpstreams() (err error) {
+func (c *persistentClient) closeUpstreams() (err error) {
if c.upstreamConfig != nil {
if err = c.upstreamConfig.Close(); err != nil {
return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err)
@@ -70,7 +244,7 @@ func (c *Client) closeUpstreams() (err error) {
}
// setSafeSearch initializes and sets the safe search filter for this client.
-func (c *Client) setSafeSearch(
+func (c *persistentClient) setSafeSearch(
conf filtering.SafeSearchConfig,
cacheSize uint,
cacheTTL time.Duration,
@@ -85,17 +259,3 @@ func (c *Client) setSafeSearch(
return nil
}
-
-// RuntimeClient is a client information about which has been obtained using the
-// source described in the Source field.
-type RuntimeClient struct {
- // WHOIS is the filtered WHOIS data of a client.
- WHOIS *whois.Info
-
- // Host is the host name of a client.
- Host string
-
- // Source is the source from which the information about the client has
- // been obtained.
- Source client.Source
-}
diff --git a/internal/home/client_internal_test.go b/internal/home/client_internal_test.go
new file mode 100644
index 00000000..c360cb19
--- /dev/null
+++ b/internal/home/client_internal_test.go
@@ -0,0 +1,124 @@
+package home
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestPersistentClient_EqualIDs(t *testing.T) {
+ const (
+ ip = "0.0.0.0"
+ ip1 = "1.1.1.1"
+ ip2 = "2.2.2.2"
+
+ cidr = "0.0.0.0/0"
+ cidr1 = "1.1.1.1/11"
+ cidr2 = "2.2.2.2/22"
+
+ mac = "00-00-00-00-00-00"
+ mac1 = "11-11-11-11-11-11"
+ mac2 = "22-22-22-22-22-22"
+
+ cli = "client0"
+ cli1 = "client1"
+ cli2 = "client2"
+ )
+
+ testCases := []struct {
+ name string
+ ids []string
+ prevIDs []string
+ want assert.BoolAssertionFunc
+ }{{
+ name: "single_ip",
+ ids: []string{ip1},
+ prevIDs: []string{ip1},
+ want: assert.True,
+ }, {
+ name: "single_ip_not_equal",
+ ids: []string{ip1},
+ prevIDs: []string{ip2},
+ want: assert.False,
+ }, {
+ name: "ips_not_equal",
+ ids: []string{ip1, ip2},
+ prevIDs: []string{ip1, ip},
+ want: assert.False,
+ }, {
+ name: "ips_mixed_equal",
+ ids: []string{ip1, ip2},
+ prevIDs: []string{ip2, ip1},
+ want: assert.True,
+ }, {
+ name: "single_subnet",
+ ids: []string{cidr1},
+ prevIDs: []string{cidr1},
+ want: assert.True,
+ }, {
+ name: "subnets_not_equal",
+ ids: []string{ip1, ip2, cidr1, cidr2},
+ prevIDs: []string{ip1, ip2, cidr1, cidr},
+ want: assert.False,
+ }, {
+ name: "subnets_mixed_equal",
+ ids: []string{ip1, ip2, cidr1, cidr2},
+ prevIDs: []string{cidr2, cidr1, ip2, ip1},
+ want: assert.True,
+ }, {
+ name: "single_mac",
+ ids: []string{mac1},
+ prevIDs: []string{mac1},
+ want: assert.True,
+ }, {
+ name: "single_mac_not_equal",
+ ids: []string{mac1},
+ prevIDs: []string{mac2},
+ want: assert.False,
+ }, {
+ name: "macs_not_equal",
+ ids: []string{ip1, ip2, cidr1, cidr2, mac1, mac2},
+ prevIDs: []string{ip1, ip2, cidr1, cidr2, mac1, mac},
+ want: assert.False,
+ }, {
+ name: "macs_mixed_equal",
+ ids: []string{ip1, ip2, cidr1, cidr2, mac1, mac2},
+ prevIDs: []string{mac2, mac1, cidr2, cidr1, ip2, ip1},
+ want: assert.True,
+ }, {
+ name: "single_client_id",
+ ids: []string{cli1},
+ prevIDs: []string{cli1},
+ want: assert.True,
+ }, {
+ name: "single_client_id_not_equal",
+ ids: []string{cli1},
+ prevIDs: []string{cli2},
+ want: assert.False,
+ }, {
+ name: "client_ids_not_equal",
+ ids: []string{ip1, ip2, cidr1, cidr2, mac1, mac2, cli1, cli2},
+ prevIDs: []string{ip1, ip2, cidr1, cidr2, mac1, mac2, cli1, cli},
+ want: assert.False,
+ }, {
+ name: "client_ids_mixed_equal",
+ ids: []string{ip1, ip2, cidr1, cidr2, mac1, mac2, cli1, cli2},
+ prevIDs: []string{cli2, cli1, mac2, mac1, cidr2, cidr1, ip2, ip1},
+ want: assert.True,
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ c := &persistentClient{}
+ err := c.setIDs(tc.ids)
+ require.NoError(t, err)
+
+ prev := &persistentClient{}
+ err = prev.setIDs(tc.prevIDs)
+ require.NoError(t, err)
+
+ tc.want(t, c.equalIDs(prev))
+ })
+ }
+}
diff --git a/internal/home/clients.go b/internal/home/clients.go
index 6d3a6d23..07256fea 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -1,7 +1,6 @@
package home
import (
- "bytes"
"fmt"
"net"
"net/netip"
@@ -20,6 +19,7 @@ import (
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/maps"
@@ -47,11 +47,11 @@ type DHCP interface {
type clientsContainer struct {
// TODO(a.garipov): Perhaps use a number of separate indices for different
// types (string, netip.Addr, and so on).
- list map[string]*Client // name -> client
- idIndex map[string]*Client // ID -> client
+ list map[string]*persistentClient // name -> client
+ idIndex map[string]*persistentClient // ID -> client
- // ipToRC is the IP address to *RuntimeClient map.
- ipToRC map[netip.Addr]*RuntimeClient
+ // ipToRC maps IP addresses to runtime client information.
+ ipToRC map[netip.Addr]*client.Runtime
allTags *stringutil.Set
@@ -102,9 +102,9 @@ func (clients *clientsContainer) Init(
log.Fatal("clients.list != nil")
}
- clients.list = make(map[string]*Client)
- clients.idIndex = make(map[string]*Client)
- clients.ipToRC = map[netip.Addr]*RuntimeClient{}
+ clients.list = map[string]*persistentClient{}
+ clients.idIndex = map[string]*persistentClient{}
+ clients.ipToRC = map[netip.Addr]*client.Runtime{}
clients.allTags = stringutil.NewSet(clientTags...)
@@ -139,6 +139,9 @@ func (clients *clientsContainer) Init(
return nil
}
+// handleHostsUpdates receives the updates from the hosts container and adds
+// them to the clients container. It's used to be called in a separate
+// goroutine.
func (clients *clientsContainer) handleHostsUpdates() {
for upd := range clients.etcHosts.Upd() {
clients.addFromHostsFile(upd)
@@ -185,6 +188,9 @@ type clientObject struct {
Tags []string `yaml:"tags"`
Upstreams []string `yaml:"upstreams"`
+ // UID is the unique identifier of the persistent client.
+ UID UID `yaml:"uid"`
+
// UpstreamsCacheSize is the DNS cache size (in bytes).
//
// TODO(d.kolyshev): Use [datasize.Bytesize].
@@ -203,66 +209,83 @@ type clientObject struct {
IgnoreStatistics bool `yaml:"ignore_statistics"`
}
+// toPersistent returns an initialized persistent client if there are no errors.
+func (o *clientObject) toPersistent(
+ filteringConf *filtering.Config,
+ allTags *stringutil.Set,
+) (cli *persistentClient, err error) {
+ cli = &persistentClient{
+ Name: o.Name,
+
+ Upstreams: o.Upstreams,
+
+ UID: o.UID,
+
+ UseOwnSettings: !o.UseGlobalSettings,
+ FilteringEnabled: o.FilteringEnabled,
+ ParentalEnabled: o.ParentalEnabled,
+ safeSearchConf: o.SafeSearchConf,
+ SafeBrowsingEnabled: o.SafeBrowsingEnabled,
+ UseOwnBlockedServices: !o.UseGlobalBlockedServices,
+ IgnoreQueryLog: o.IgnoreQueryLog,
+ IgnoreStatistics: o.IgnoreStatistics,
+ UpstreamsCacheEnabled: o.UpstreamsCacheEnabled,
+ UpstreamsCacheSize: o.UpstreamsCacheSize,
+ }
+
+ err = cli.setIDs(o.IDs)
+ if err != nil {
+ return nil, fmt.Errorf("parsing ids: %w", err)
+ }
+
+ if (cli.UID == UID{}) {
+ cli.UID, err = NewUID()
+ if err != nil {
+ return nil, fmt.Errorf("generating uid: %w", err)
+ }
+ }
+
+ if o.SafeSearchConf.Enabled {
+ o.SafeSearchConf.CustomResolver = safeSearchResolver{}
+
+ err = cli.setSafeSearch(
+ o.SafeSearchConf,
+ filteringConf.SafeSearchCacheSize,
+ time.Minute*time.Duration(filteringConf.CacheTime),
+ )
+ if err != nil {
+ return nil, fmt.Errorf("init safesearch %q: %w", cli.Name, err)
+ }
+ }
+
+ err = o.BlockedServices.Validate()
+ if err != nil {
+ return nil, fmt.Errorf("init blocked services %q: %w", cli.Name, err)
+ }
+
+ cli.BlockedServices = o.BlockedServices.Clone()
+
+ cli.setTags(o.Tags, allTags)
+
+ return cli, nil
+}
+
// addFromConfig initializes the clients container with objects from the
// configuration file.
func (clients *clientsContainer) addFromConfig(
objects []*clientObject,
filteringConf *filtering.Config,
) (err error) {
- for _, o := range objects {
- cli := &Client{
- Name: o.Name,
-
- IDs: o.IDs,
- Upstreams: o.Upstreams,
-
- UseOwnSettings: !o.UseGlobalSettings,
- FilteringEnabled: o.FilteringEnabled,
- ParentalEnabled: o.ParentalEnabled,
- safeSearchConf: o.SafeSearchConf,
- SafeBrowsingEnabled: o.SafeBrowsingEnabled,
- UseOwnBlockedServices: !o.UseGlobalBlockedServices,
- IgnoreQueryLog: o.IgnoreQueryLog,
- IgnoreStatistics: o.IgnoreStatistics,
- UpstreamsCacheEnabled: o.UpstreamsCacheEnabled,
- UpstreamsCacheSize: o.UpstreamsCacheSize,
- }
-
- if o.SafeSearchConf.Enabled {
- o.SafeSearchConf.CustomResolver = safeSearchResolver{}
-
- err = cli.setSafeSearch(
- o.SafeSearchConf,
- filteringConf.SafeSearchCacheSize,
- time.Minute*time.Duration(filteringConf.CacheTime),
- )
- if err != nil {
- log.Error("clients: init client safesearch %q: %s", cli.Name, err)
-
- continue
- }
- }
-
- err = o.BlockedServices.Validate()
+ for i, o := range objects {
+ var cli *persistentClient
+ cli, err = o.toPersistent(filteringConf, clients.allTags)
if err != nil {
- return fmt.Errorf("clients: init client blocked services %q: %w", cli.Name, err)
+ return fmt.Errorf("clients: init persistent client at index %d: %w", i, err)
}
- cli.BlockedServices = o.BlockedServices.Clone()
-
- for _, t := range o.Tags {
- if clients.allTags.Has(t) {
- cli.Tags = append(cli.Tags, t)
- } else {
- log.Info("clients: skipping unknown tag %q", t)
- }
- }
-
- slices.Sort(cli.Tags)
-
- _, err = clients.Add(cli)
+ _, err = clients.add(cli)
if err != nil {
- log.Error("clients: adding clients %s: %s", cli.Name, err)
+ log.Error("clients: adding client at index %d %s: %s", i, cli.Name, err)
}
}
@@ -282,10 +305,12 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
BlockedServices: cli.BlockedServices.Clone(),
- IDs: stringutil.CloneSlice(cli.IDs),
+ IDs: cli.ids(),
Tags: stringutil.CloneSlice(cli.Tags),
Upstreams: stringutil.CloneSlice(cli.Upstreams),
+ UID: cli.UID,
+
UseGlobalSettings: !cli.UseOwnSettings,
FilteringEnabled: cli.FilteringEnabled,
ParentalEnabled: cli.ParentalEnabled,
@@ -338,7 +363,7 @@ func (clients *clientsContainer) clientSource(ip netip.Addr) (src client.Source)
rc, ok := clients.ipToRC[ip]
if ok {
- src = rc.Source
+ src, _ = rc.Info()
}
if src < client.SourceDHCP && clients.dhcp.HostByIP(ip) != "" {
@@ -348,10 +373,10 @@ func (clients *clientsContainer) clientSource(ip netip.Addr) (src client.Source)
return src
}
-// findMultiple is a wrapper around Find to make it a valid client finder for
-// the query log. c is never nil; if no information about the client is found,
-// it returns an artificial client record by only setting the blocking-related
-// fields. err is always nil.
+// findMultiple is a wrapper around [clientsContainer.find] to make it a valid
+// client finder for the query log. c is never nil; if no information about the
+// client is found, it returns an artificial client record by only setting the
+// blocking-related fields. err is always nil.
func (clients *clientsContainer) findMultiple(ids []string) (c *querylog.Client, err error) {
var artClient *querylog.Client
var art bool
@@ -385,20 +410,22 @@ func (clients *clientsContainer) clientOrArtificial(
}
}()
- client, ok := clients.Find(id)
+ cli, ok := clients.find(id)
if ok {
return &querylog.Client{
- Name: client.Name,
- IgnoreQueryLog: client.IgnoreQueryLog,
+ Name: cli.Name,
+ IgnoreQueryLog: cli.IgnoreQueryLog,
}, false
}
- var rc *RuntimeClient
+ var rc *client.Runtime
rc, ok = clients.findRuntimeClient(ip)
if ok {
+ _, host := rc.Info()
+
return &querylog.Client{
- Name: rc.Host,
- WHOIS: rc.WHOIS,
+ Name: host,
+ WHOIS: rc.WHOIS(),
}, false
}
@@ -407,8 +434,8 @@ func (clients *clientsContainer) clientOrArtificial(
}, true
}
-// Find returns a shallow copy of the client if there is one found.
-func (clients *clientsContainer) Find(id string) (c *Client, ok bool) {
+// find returns a shallow copy of the client if there is one found.
+func (clients *clientsContainer) find(id string) (c *persistentClient, ok bool) {
clients.lock.Lock()
defer clients.lock.Unlock()
@@ -417,12 +444,12 @@ func (clients *clientsContainer) Find(id string) (c *Client, ok bool) {
return nil, false
}
- return c.ShallowClone(), true
+ return c.shallowClone(), true
}
-// shouldCountClient is a wrapper around Find to make it a valid client
-// information finder for the statistics. If no information about the client
-// is found, it returns true.
+// shouldCountClient is a wrapper around [clientsContainer.find] to make it a
+// valid client information finder for the statistics. If no information about
+// the client is found, it returns true.
func (clients *clientsContainer) shouldCountClient(ids []string) (y bool) {
clients.lock.Lock()
defer clients.lock.Unlock()
@@ -490,7 +517,7 @@ func (clients *clientsContainer) UpstreamConfigByID(
// findLocked searches for a client by its ID. clients.lock is expected to be
// locked.
-func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
+func (clients *clientsContainer) findLocked(id string) (c *persistentClient, ok bool) {
c, ok = clients.idIndex[id]
if ok {
return c, true
@@ -502,13 +529,7 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
}
for _, c = range clients.list {
- for _, id := range c.IDs {
- var subnet netip.Prefix
- subnet, err = netip.ParsePrefix(id)
- if err != nil {
- continue
- }
-
+ for _, subnet := range c.Subnets {
if subnet.Contains(ip) {
return c, true
}
@@ -521,22 +542,16 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
// findDHCP searches for a client by its MAC, if the DHCP server is active and
// there is such client. clients.lock is expected to be locked.
-func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *Client, ok bool) {
+func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *persistentClient, ok bool) {
foundMAC := clients.dhcp.MACByIP(ip)
if foundMAC == nil {
return nil, false
}
for _, c = range clients.list {
- for _, id := range c.IDs {
- mac, err := net.ParseMAC(id)
- if err != nil {
- continue
- }
-
- if bytes.Equal(mac, foundMAC) {
- return c, true
- }
+ _, found := slices.BinarySearchFunc(c.MACs, foundMAC, slices.Compare[net.HardwareAddr])
+ if found {
+ return c, true
}
}
@@ -545,7 +560,7 @@ func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *Client, ok bool) {
// runtimeClient returns a runtime client from internal index. Note that it
// doesn't include DHCP clients.
-func (clients *clientsContainer) runtimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) {
+func (clients *clientsContainer) runtimeClient(ip netip.Addr) (rc *client.Runtime, ok bool) {
if ip == (netip.Addr{}) {
return nil, false
}
@@ -559,52 +574,43 @@ func (clients *clientsContainer) runtimeClient(ip netip.Addr) (rc *RuntimeClient
}
// findRuntimeClient finds a runtime client by their IP.
-func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) {
- if rc, ok = clients.runtimeClient(ip); ok && rc.Source > client.SourceDHCP {
- return rc, ok
- }
-
+func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *client.Runtime, ok bool) {
+ rc, ok = clients.runtimeClient(ip)
host := clients.dhcp.HostByIP(ip)
- if host == "" {
- return rc, ok
+
+ if host != "" {
+ if !ok {
+ rc = &client.Runtime{}
+ }
+
+ rc.SetInfo(client.SourceDHCP, []string{host})
+
+ return rc, true
}
- return &RuntimeClient{
- Host: host,
- Source: client.SourceDHCP,
- WHOIS: &whois.Info{},
- }, true
+ return rc, ok
}
-// check validates the client.
-func (clients *clientsContainer) check(c *Client) (err error) {
+// check validates the client. It also sorts the client tags.
+func (clients *clientsContainer) check(c *persistentClient) (err error) {
switch {
case c == nil:
return errors.Error("client is nil")
case c.Name == "":
return errors.Error("invalid name")
- case len(c.IDs) == 0:
+ case c.idsLen() == 0:
return errors.Error("id required")
default:
// Go on.
}
- for i, id := range c.IDs {
- var norm string
- norm, err = normalizeClientIdentifier(id)
- if err != nil {
- return fmt.Errorf("client at index %d: %w", i, err)
- }
-
- c.IDs[i] = norm
- }
-
for _, t := range c.Tags {
if !clients.allTags.Has(t) {
return fmt.Errorf("invalid tag: %q", t)
}
}
+ // TODO(s.chzhen): Move to the constructor.
slices.Sort(c.Tags)
err = dnsforward.ValidateUpstreams(c.Upstreams)
@@ -615,38 +621,9 @@ func (clients *clientsContainer) check(c *Client) (err error) {
return nil
}
-// normalizeClientIdentifier returns a normalized version of idStr. If idStr
-// cannot be normalized, it returns an error.
-func normalizeClientIdentifier(idStr string) (norm string, err error) {
- if idStr == "" {
- return "", errors.Error("clientid is empty")
- }
-
- var ip netip.Addr
- if ip, err = netip.ParseAddr(idStr); err == nil {
- return ip.String(), nil
- }
-
- var subnet netip.Prefix
- if subnet, err = netip.ParsePrefix(idStr); err == nil {
- return subnet.String(), nil
- }
-
- var mac net.HardwareAddr
- if mac, err = net.ParseMAC(idStr); err == nil {
- return mac.String(), nil
- }
-
- if err = dnsforward.ValidateClientID(idStr); err == nil {
- return strings.ToLower(idStr), nil
- }
-
- return "", fmt.Errorf("bad client identifier %q", idStr)
-}
-
-// Add adds a new client object. ok is false if such client already exists or
+// add adds a new client object. ok is false if such client already exists or
// if an error occurred.
-func (clients *clientsContainer) Add(c *Client) (ok bool, err error) {
+func (clients *clientsContainer) add(c *persistentClient) (ok bool, err error) {
err = clients.check(c)
if err != nil {
return false, err
@@ -662,50 +639,52 @@ func (clients *clientsContainer) Add(c *Client) (ok bool, err error) {
}
// check ID index
- for _, id := range c.IDs {
- var c2 *Client
+ ids := c.ids()
+ for _, id := range ids {
+ var c2 *persistentClient
c2, ok = clients.idIndex[id]
if ok {
return false, fmt.Errorf("another client uses the same ID (%q): %q", id, c2.Name)
}
}
- clients.add(c)
+ clients.addLocked(c)
- log.Debug("clients: added %q: ID:%q [%d]", c.Name, c.IDs, len(clients.list))
+ log.Debug("clients: added %q: ID:%q [%d]", c.Name, ids, len(clients.list))
return true, nil
}
-// add c to the indexes. clients.lock is expected to be locked.
-func (clients *clientsContainer) add(c *Client) {
+// addLocked c to the indexes. clients.lock is expected to be locked.
+func (clients *clientsContainer) addLocked(c *persistentClient) {
// update Name index
clients.list[c.Name] = c
// update ID index
- for _, id := range c.IDs {
+ for _, id := range c.ids() {
clients.idIndex[id] = c
}
}
-// Del removes a client. ok is false if there is no such client.
-func (clients *clientsContainer) Del(name string) (ok bool) {
+// remove removes a client. ok is false if there is no such client.
+func (clients *clientsContainer) remove(name string) (ok bool) {
clients.lock.Lock()
defer clients.lock.Unlock()
- var c *Client
+ var c *persistentClient
c, ok = clients.list[name]
if !ok {
return false
}
- clients.del(c)
+ clients.removeLocked(c)
return true
}
-// del removes c from the indexes. clients.lock is expected to be locked.
-func (clients *clientsContainer) del(c *Client) {
+// removeLocked removes c from the indexes. clients.lock is expected to be
+// locked.
+func (clients *clientsContainer) removeLocked(c *persistentClient) {
if err := c.closeUpstreams(); err != nil {
log.Error("client container: removing client %s: %s", c.Name, err)
}
@@ -714,13 +693,13 @@ func (clients *clientsContainer) del(c *Client) {
delete(clients.list, c.Name)
// Update the ID index.
- for _, id := range c.IDs {
+ for _, id := range c.ids() {
delete(clients.idIndex, id)
}
}
-// Update updates a client by its name.
-func (clients *clientsContainer) Update(prev, c *Client) (err error) {
+// update updates a client by its name.
+func (clients *clientsContainer) update(prev, c *persistentClient) (err error) {
err = clients.check(c)
if err != nil {
// Don't wrap the error since it's informative enough as is.
@@ -738,18 +717,23 @@ func (clients *clientsContainer) Update(prev, c *Client) (err error) {
}
}
+ if c.equalIDs(prev) {
+ clients.removeLocked(prev)
+ clients.addLocked(c)
+
+ return nil
+ }
+
// Check the ID index.
- if !slices.Equal(prev.IDs, c.IDs) {
- for _, id := range c.IDs {
- existing, ok := clients.idIndex[id]
- if ok && existing != prev {
- return fmt.Errorf("id %q is used by client with name %q", id, existing.Name)
- }
+ for _, id := range c.ids() {
+ existing, ok := clients.idIndex[id]
+ if ok && existing != prev {
+ return fmt.Errorf("id %q is used by client with name %q", id, existing.Name)
}
}
- clients.del(prev)
- clients.add(c)
+ clients.removeLocked(prev)
+ clients.addLocked(c)
return nil
}
@@ -764,23 +748,20 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
return
}
- // TODO(e.burkov): Consider storing WHOIS information separately and
- // potentially get rid of [RuntimeClient].
rc, ok := clients.ipToRC[ip]
if !ok {
// Create a RuntimeClient implicitly so that we don't do this check
// again.
- rc = &RuntimeClient{
- Source: client.SourceWHOIS,
- }
+ rc = &client.Runtime{}
clients.ipToRC[ip] = rc
log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi)
} else {
- log.Debug("clients: set whois info for runtime client %s: %+v", rc.Host, wi)
+ host, _ := rc.Info()
+ log.Debug("clients: set whois info for runtime client %s: %+v", host, wi)
}
- rc.WHOIS = wi
+ rc.SetWHOIS(wi)
}
// addHost adds a new IP-hostname pairing. The priorities of the sources are
@@ -839,18 +820,13 @@ func (clients *clientsContainer) addHostLocked(
}
}
- rc = &RuntimeClient{
- WHOIS: &whois.Info{},
- }
+ rc = &client.Runtime{}
clients.ipToRC[ip] = rc
- } else if src < rc.Source {
- return false
}
- rc.Host = host
- rc.Source = src
+ rc.SetInfo(src, []string{host})
- log.Debug("clients: added %s -> %q [%d]", ip, host, len(clients.ipToRC))
+ log.Debug("clients: adding client info %s -> %q %q [%d]", ip, src, host, len(clients.ipToRC))
return true
}
@@ -859,7 +835,8 @@ func (clients *clientsContainer) addHostLocked(
func (clients *clientsContainer) rmHostsBySrc(src client.Source) {
n := 0
for ip, rc := range clients.ipToRC {
- if rc.Source == src {
+ rc.Unset(src)
+ if rc.IsEmpty() {
delete(clients.ipToRC, ip)
n++
}
@@ -870,21 +847,24 @@ func (clients *clientsContainer) rmHostsBySrc(src client.Source) {
// addFromHostsFile fills the client-hostname pairing index from the system's
// hosts files.
-func (clients *clientsContainer) addFromHostsFile(hosts aghnet.Hosts) {
+func (clients *clientsContainer) addFromHostsFile(hosts *hostsfile.DefaultStorage) {
clients.lock.Lock()
defer clients.lock.Unlock()
clients.rmHostsBySrc(client.SourceHostsFile)
n := 0
- for addr, rec := range hosts {
+ hosts.RangeNames(func(addr netip.Addr, names []string) (cont bool) {
// Only the first name of the first record is considered a canonical
// hostname for the IP address.
//
// TODO(e.burkov): Consider using all the names from all the records.
- clients.addHostLocked(addr, rec[0].Names[0], client.SourceHostsFile)
- n++
- }
+ if clients.addHostLocked(addr, names[0], client.SourceHostsFile) {
+ n++
+ }
+
+ return true
+ })
log.Debug("clients: added %d client aliases from system hosts file", n)
}
@@ -926,7 +906,7 @@ func (clients *clientsContainer) addFromSystemARP() {
// the persistent clients.
func (clients *clientsContainer) close() (err error) {
persistent := maps.Values(clients.list)
- slices.SortFunc(persistent, func(a, b *Client) (res int) {
+ slices.SortFunc(persistent, func(a, b *persistentClient) (res int) {
return strings.Compare(a.Name, b.Name)
})
diff --git a/internal/home/clients_internal_test.go b/internal/home/clients_internal_test.go
index 30d735bb..07332ecf 100644
--- a/internal/home/clients_internal_test.go
+++ b/internal/home/clients_internal_test.go
@@ -60,63 +60,65 @@ func TestClients(t *testing.T) {
cli1 = "1.1.1.1"
cli2 = "2.2.2.2"
- cliNoneIP = netip.MustParseAddr(cliNone)
- cli1IP = netip.MustParseAddr(cli1)
- cli2IP = netip.MustParseAddr(cli2)
+ cli1IP = netip.MustParseAddr(cli1)
+ cli2IP = netip.MustParseAddr(cli2)
+
+ cliIPv6 = netip.MustParseAddr("1:2:3::4")
)
- c := &Client{
- IDs: []string{cli1, "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
+ c := &persistentClient{
Name: "client1",
+ IPs: []netip.Addr{cli1IP, cliIPv6},
}
- ok, err := clients.Add(c)
+ ok, err := clients.add(c)
require.NoError(t, err)
assert.True(t, ok)
- c = &Client{
- IDs: []string{cli2},
+ c = &persistentClient{
Name: "client2",
+ IPs: []netip.Addr{cli2IP},
}
- ok, err = clients.Add(c)
+ ok, err = clients.add(c)
require.NoError(t, err)
assert.True(t, ok)
- c, ok = clients.Find(cli1)
+ c, ok = clients.find(cli1)
require.True(t, ok)
assert.Equal(t, "client1", c.Name)
- c, ok = clients.Find("1:2:3::4")
+ c, ok = clients.find("1:2:3::4")
require.True(t, ok)
assert.Equal(t, "client1", c.Name)
- c, ok = clients.Find(cli2)
+ c, ok = clients.find(cli2)
require.True(t, ok)
assert.Equal(t, "client2", c.Name)
- assert.Equal(t, clients.clientSource(cliNoneIP), client.SourceNone)
+ _, ok = clients.find(cliNone)
+ assert.False(t, ok)
+
assert.Equal(t, clients.clientSource(cli1IP), client.SourcePersistent)
assert.Equal(t, clients.clientSource(cli2IP), client.SourcePersistent)
})
t.Run("add_fail_name", func(t *testing.T) {
- ok, err := clients.Add(&Client{
- IDs: []string{"1.2.3.5"},
+ ok, err := clients.add(&persistentClient{
Name: "client1",
+ IPs: []netip.Addr{netip.MustParseAddr("1.2.3.5")},
})
require.NoError(t, err)
assert.False(t, ok)
})
t.Run("add_fail_ip", func(t *testing.T) {
- ok, err := clients.Add(&Client{
- IDs: []string{"2.2.2.2"},
+ ok, err := clients.add(&persistentClient{
Name: "client3",
})
require.Error(t, err)
@@ -124,8 +126,7 @@ func TestClients(t *testing.T) {
})
t.Run("update_fail_ip", func(t *testing.T) {
- err := clients.Update(&Client{Name: "client1"}, &Client{
- IDs: []string{"2.2.2.2"},
+ err := clients.update(&persistentClient{Name: "client1"}, &persistentClient{
Name: "client1",
})
assert.Error(t, err)
@@ -136,33 +137,34 @@ func TestClients(t *testing.T) {
cliOld = "1.1.1.1"
cliNew = "1.1.1.2"
- cliOldIP = netip.MustParseAddr(cliOld)
cliNewIP = netip.MustParseAddr(cliNew)
)
prev, ok := clients.list["client1"]
require.True(t, ok)
- err := clients.Update(prev, &Client{
- IDs: []string{cliNew},
+ err := clients.update(prev, &persistentClient{
Name: "client1",
+ IPs: []netip.Addr{cliNewIP},
})
require.NoError(t, err)
- assert.Equal(t, clients.clientSource(cliOldIP), client.SourceNone)
+ _, ok = clients.find(cliOld)
+ assert.False(t, ok)
+
assert.Equal(t, clients.clientSource(cliNewIP), client.SourcePersistent)
prev, ok = clients.list["client1"]
require.True(t, ok)
- err = clients.Update(prev, &Client{
- IDs: []string{cliNew},
+ err = clients.update(prev, &persistentClient{
Name: "client1-renamed",
+ IPs: []netip.Addr{cliNewIP},
UseOwnSettings: true,
})
require.NoError(t, err)
- c, ok := clients.Find(cliNew)
+ c, ok := clients.find(cliNew)
require.True(t, ok)
assert.Equal(t, "client1-renamed", c.Name)
@@ -173,20 +175,21 @@ func TestClients(t *testing.T) {
assert.Nil(t, nilCli)
- require.Len(t, c.IDs, 1)
+ require.Len(t, c.ids(), 1)
- assert.Equal(t, cliNew, c.IDs[0])
+ assert.Equal(t, cliNewIP, c.IPs[0])
})
t.Run("del_success", func(t *testing.T) {
- ok := clients.Del("client1-renamed")
+ ok := clients.remove("client1-renamed")
require.True(t, ok)
- assert.Equal(t, clients.clientSource(netip.MustParseAddr("1.1.1.2")), client.SourceNone)
+ _, ok = clients.find("1.1.1.2")
+ assert.False(t, ok)
})
t.Run("del_fail", func(t *testing.T) {
- ok := clients.Del("client3")
+ ok := clients.remove("client3")
assert.False(t, ok)
})
@@ -215,10 +218,12 @@ func TestClients(t *testing.T) {
assert.Equal(t, clients.clientSource(ip), client.SourceDHCP)
})
- t.Run("addhost_fail", func(t *testing.T) {
+ t.Run("addhost_priority", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1")
ok := clients.addHost(ip, "host1", client.SourceRDNS)
- assert.False(t, ok)
+ assert.True(t, ok)
+
+ assert.Equal(t, client.SourceHostsFile, clients.clientSource(ip))
})
}
@@ -235,7 +240,7 @@ func TestClientsWHOIS(t *testing.T) {
rc := clients.ipToRC[ip]
require.NotNil(t, rc)
- assert.Equal(t, rc.WHOIS, whois)
+ assert.Equal(t, whois, rc.WHOIS())
})
t.Run("existing_auto-client", func(t *testing.T) {
@@ -247,15 +252,15 @@ func TestClientsWHOIS(t *testing.T) {
rc := clients.ipToRC[ip]
require.NotNil(t, rc)
- assert.Equal(t, rc.WHOIS, whois)
+ assert.Equal(t, whois, rc.WHOIS())
})
t.Run("can't_set_manually-added", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.2")
- ok, err := clients.Add(&Client{
- IDs: []string{"1.1.1.2"},
+ ok, err := clients.add(&persistentClient{
Name: "client1",
+ IPs: []netip.Addr{netip.MustParseAddr("1.1.1.2")},
})
require.NoError(t, err)
assert.True(t, ok)
@@ -264,7 +269,7 @@ func TestClientsWHOIS(t *testing.T) {
rc := clients.ipToRC[ip]
require.Nil(t, rc)
- assert.True(t, clients.Del("client1"))
+ assert.True(t, clients.remove("client1"))
})
}
@@ -275,9 +280,11 @@ func TestClientsAddExisting(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1")
// Add a client.
- ok, err := clients.Add(&Client{
- IDs: []string{ip.String(), "1:2:3::4", "aa:aa:aa:aa:aa:aa", "2.2.2.0/24"},
- Name: "client1",
+ ok, err := clients.add(&persistentClient{
+ Name: "client1",
+ IPs: []netip.Addr{ip, netip.MustParseAddr("1:2:3::4")},
+ Subnets: []netip.Prefix{netip.MustParsePrefix("2.2.2.0/24")},
+ MACs: []net.HardwareAddr{{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}},
})
require.NoError(t, err)
assert.True(t, ok)
@@ -323,17 +330,17 @@ func TestClientsAddExisting(t *testing.T) {
require.NoError(t, err)
// Add a new client with the same IP as for a client with MAC.
- ok, err := clients.Add(&Client{
- IDs: []string{ip.String()},
+ ok, err := clients.add(&persistentClient{
Name: "client2",
+ IPs: []netip.Addr{ip},
})
require.NoError(t, err)
assert.True(t, ok)
// Add a new client with the IP from the first client's IP range.
- ok, err = clients.Add(&Client{
- IDs: []string{"2.2.2.2"},
+ ok, err = clients.add(&persistentClient{
Name: "client3",
+ IPs: []netip.Addr{netip.MustParseAddr("2.2.2.2")},
})
require.NoError(t, err)
assert.True(t, ok)
@@ -344,9 +351,9 @@ func TestClientsCustomUpstream(t *testing.T) {
clients := newClientsContainer(t)
// Add client with upstreams.
- ok, err := clients.Add(&Client{
- IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
+ ok, err := clients.add(&persistentClient{
Name: "client1",
+ IPs: []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1:2:3::4")},
Upstreams: []string{
"1.1.1.1",
"[/example.org/]8.8.8.8",
diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go
index ad51e944..3f2918ca 100644
--- a/internal/home/clientshttp.go
+++ b/internal/home/clientshttp.go
@@ -61,6 +61,7 @@ type clientJSON struct {
UpstreamsCacheEnabled aghalg.NullBool `json:"upstreams_cache_enabled"`
}
+// runtimeClientJSON is a JSON representation of the [client.Runtime].
type runtimeClientJSON struct {
WHOIS *whois.Info `json:"whois_info"`
@@ -69,12 +70,25 @@ type runtimeClientJSON struct {
Source client.Source `json:"source"`
}
+// clientListJSON contains lists of persistent clients, runtime clients and also
+// supported tags.
type clientListJSON struct {
Clients []*clientJSON `json:"clients"`
RuntimeClients []runtimeClientJSON `json:"auto_clients"`
Tags []string `json:"supported_tags"`
}
+// whoisOrEmpty returns a WHOIS client information or a pointer to an empty
+// struct. Frontend expects a non-nil value.
+func whoisOrEmpty(r *client.Runtime) (info *whois.Info) {
+ info = r.WHOIS()
+ if info != nil {
+ return info
+ }
+
+ return &whois.Info{}
+}
+
// handleGetClients is the handler for GET /control/clients HTTP API.
func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http.Request) {
data := clientListJSON{}
@@ -88,11 +102,11 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
}
for ip, rc := range clients.ipToRC {
+ src, host := rc.Info()
cj := runtimeClientJSON{
- WHOIS: rc.WHOIS,
-
- Name: rc.Host,
- Source: rc.Source,
+ WHOIS: whoisOrEmpty(rc),
+ Name: host,
+ Source: src,
IP: ip,
}
@@ -115,32 +129,36 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
aghhttp.WriteJSONResponseOK(w, r, data)
}
-// jsonToClient converts JSON object to Client object.
-func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *Client, err error) {
- safeSearchConf := copySafeSearch(cj.SafeSearchConf, cj.SafeSearchEnabled)
+// initPrev initializes the persistent client with the default or previous
+// client properties.
+func initPrev(cj clientJSON, prev *persistentClient) (c *persistentClient, err error) {
+ var (
+ uid UID
+ ignoreQueryLog bool
+ ignoreStatistics bool
+ upsCacheEnabled bool
+ upsCacheSize uint32
+ )
+
+ if prev != nil {
+ uid = prev.UID
+ ignoreQueryLog = prev.IgnoreQueryLog
+ ignoreStatistics = prev.IgnoreStatistics
+ upsCacheEnabled = prev.UpstreamsCacheEnabled
+ upsCacheSize = prev.UpstreamsCacheSize
+ }
- var ignoreQueryLog bool
if cj.IgnoreQueryLog != aghalg.NBNull {
ignoreQueryLog = cj.IgnoreQueryLog == aghalg.NBTrue
- } else if prev != nil {
- ignoreQueryLog = prev.IgnoreQueryLog
}
- var ignoreStatistics bool
if cj.IgnoreStatistics != aghalg.NBNull {
ignoreStatistics = cj.IgnoreStatistics == aghalg.NBTrue
- } else if prev != nil {
- ignoreStatistics = prev.IgnoreStatistics
}
- var upsCacheEnabled bool
- var upsCacheSize uint32
if cj.UpstreamsCacheEnabled != aghalg.NBNull {
upsCacheEnabled = cj.UpstreamsCacheEnabled == aghalg.NBTrue
upsCacheSize = cj.UpstreamsCacheSize
- } else if prev != nil {
- upsCacheEnabled = prev.UpstreamsCacheEnabled
- upsCacheSize = prev.UpstreamsCacheSize
}
svcs, err := copyBlockedServices(cj.Schedule, cj.BlockedServices, prev)
@@ -148,31 +166,54 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
return nil, fmt.Errorf("invalid blocked services: %w", err)
}
- c = &Client{
- safeSearchConf: safeSearchConf,
+ if (uid == UID{}) {
+ uid, err = NewUID()
+ if err != nil {
+ return nil, fmt.Errorf("generating uid: %w", err)
+ }
+ }
- Name: cj.Name,
-
- BlockedServices: svcs,
-
- IDs: cj.IDs,
- Tags: cj.Tags,
- Upstreams: cj.Upstreams,
-
- UseOwnSettings: !cj.UseGlobalSettings,
- FilteringEnabled: cj.FilteringEnabled,
- ParentalEnabled: cj.ParentalEnabled,
- SafeBrowsingEnabled: cj.SafeBrowsingEnabled,
- UseOwnBlockedServices: !cj.UseGlobalBlockedServices,
+ return &persistentClient{
+ BlockedServices: svcs,
+ UID: uid,
IgnoreQueryLog: ignoreQueryLog,
IgnoreStatistics: ignoreStatistics,
UpstreamsCacheEnabled: upsCacheEnabled,
UpstreamsCacheSize: upsCacheSize,
+ }, nil
+}
+
+// jsonToClient converts JSON object to persistent client object if there are no
+// errors.
+func (clients *clientsContainer) jsonToClient(
+ cj clientJSON,
+ prev *persistentClient,
+) (c *persistentClient, err error) {
+ c, err = initPrev(cj, prev)
+ if err != nil {
+ // Don't wrap the error since it's informative enough as is.
+ return nil, err
}
- if safeSearchConf.Enabled {
+ err = c.setIDs(cj.IDs)
+ if err != nil {
+ // Don't wrap the error since it's informative enough as is.
+ return nil, err
+ }
+
+ c.safeSearchConf = copySafeSearch(cj.SafeSearchConf, cj.SafeSearchEnabled)
+ c.Name = cj.Name
+ c.Tags = cj.Tags
+ c.Upstreams = cj.Upstreams
+ c.UseOwnSettings = !cj.UseGlobalSettings
+ c.FilteringEnabled = cj.FilteringEnabled
+ c.ParentalEnabled = cj.ParentalEnabled
+ c.SafeBrowsingEnabled = cj.SafeBrowsingEnabled
+ c.UseOwnBlockedServices = !cj.UseGlobalBlockedServices
+
+ if c.safeSearchConf.Enabled {
err = c.setSafeSearch(
- safeSearchConf,
+ c.safeSearchConf,
clients.safeSearchCacheSize,
clients.safeSearchCacheTTL,
)
@@ -217,7 +258,7 @@ func copySafeSearch(
func copyBlockedServices(
sch *schedule.Weekly,
svcStrs []string,
- prev *Client,
+ prev *persistentClient,
) (svcs *filtering.BlockedServices, err error) {
var weekly *schedule.Weekly
if sch != nil {
@@ -241,8 +282,8 @@ func copyBlockedServices(
return svcs, nil
}
-// clientToJSON converts Client object to JSON.
-func clientToJSON(c *Client) (cj *clientJSON) {
+// clientToJSON converts persistent client object to JSON object.
+func clientToJSON(c *persistentClient) (cj *clientJSON) {
// TODO(d.kolyshev): Remove after cleaning the deprecated
// [clientJSON.SafeSearchEnabled] field.
cloneVal := c.safeSearchConf
@@ -250,7 +291,7 @@ func clientToJSON(c *Client) (cj *clientJSON) {
return &clientJSON{
Name: c.Name,
- IDs: c.IDs,
+ IDs: c.ids(),
Tags: c.Tags,
UseGlobalSettings: !c.UseOwnSettings,
FilteringEnabled: c.FilteringEnabled,
@@ -291,7 +332,7 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.
return
}
- ok, err := clients.Add(c)
+ ok, err := clients.add(c)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
@@ -323,7 +364,7 @@ func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.
return
}
- if !clients.Del(cj.Name) {
+ if !clients.remove(cj.Name) {
aghhttp.Error(r, w, http.StatusBadRequest, "Client not found")
return
@@ -332,6 +373,7 @@ func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.
onConfigModified()
}
+// updateJSON contains the name and data of the updated persistent client.
type updateJSON struct {
Name string `json:"name"`
Data clientJSON `json:"data"`
@@ -355,7 +397,7 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
return
}
- var prev *Client
+ var prev *persistentClient
var ok bool
func() {
@@ -367,6 +409,8 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
if !ok {
aghhttp.Error(r, w, http.StatusBadRequest, "client not found")
+
+ return
}
c, err := clients.jsonToClient(dj.Data, prev)
@@ -376,7 +420,7 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
return
}
- err = clients.Update(prev, c)
+ err = clients.update(prev, c)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
@@ -397,7 +441,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
}
ip, _ := netip.ParseAddr(idStr)
- c, ok := clients.Find(idStr)
+ c, ok := clients.find(idStr)
var cj *clientJSON
if !ok {
cj = clients.findRuntime(ip, idStr)
@@ -437,10 +481,11 @@ func (clients *clientsContainer) findRuntime(ip netip.Addr, idStr string) (cj *c
return cj
}
+ _, host := rc.Info()
cj = &clientJSON{
- Name: rc.Host,
+ Name: host,
IDs: []string{idStr},
- WHOIS: rc.WHOIS,
+ WHOIS: whoisOrEmpty(rc),
}
disallowed, rule := clients.dnsServer.IsBlockedClient(ip, idStr)
diff --git a/internal/home/config.go b/internal/home/config.go
index e78bf8e9..fac08df0 100644
--- a/internal/home/config.go
+++ b/internal/home/config.go
@@ -10,7 +10,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
- "github.com/AdguardTeam/AdGuardHome/internal/confmigrate"
+ "github.com/AdguardTeam/AdGuardHome/internal/configmigrate"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
@@ -20,6 +20,7 @@ import (
"github.com/AdguardTeam/dnsproxy/fastip"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
+ "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/google/renameio/v2/maybe"
yaml "gopkg.in/yaml.v3"
@@ -149,7 +150,7 @@ type configuration struct {
sync.RWMutex `yaml:"-"`
// SchemaVersion is the version of the configuration schema. See
- // [confmigrate.LastSchemaVersion].
+ // [configmigrate.LastSchemaVersion].
SchemaVersion uint `yaml:"schema_version"`
}
@@ -200,7 +201,7 @@ type dnsConfig struct {
// PrivateNets is the set of IP networks for which the private reverse DNS
// resolver should be used.
- PrivateNets []string `yaml:"private_networks"`
+ PrivateNets []netutil.Prefix `yaml:"private_networks"`
// UsePrivateRDNS defines if the PTR requests for unknown addresses from
// locally-served networks should be resolved via private PTR resolvers.
@@ -267,7 +268,7 @@ type queryLogConfig struct {
// MemSize is the number of entries kept in memory before they are flushed
// to disk.
- MemSize int `yaml:"size_memory"`
+ MemSize uint `yaml:"size_memory"`
// Enabled defines if the query log is enabled.
Enabled bool `yaml:"enabled"`
@@ -315,14 +316,18 @@ var config = &configuration{
RatelimitSubnetLenIPv4: 24,
RatelimitSubnetLenIPv6: 56,
RefuseAny: true,
- AllServers: false,
+ UpstreamMode: dnsforward.UpstreamModeLoadBalance,
HandleDDR: true,
FastestTimeout: timeutil.Duration{
Duration: fastip.DefaultPingWaitTimeout,
},
- TrustedProxies: []string{"127.0.0.0/8", "::1/128"},
- CacheSize: 4 * 1024 * 1024,
+ TrustedProxies: []netutil.Prefix{{
+ Prefix: netip.MustParsePrefix("127.0.0.0/8"),
+ }, {
+ Prefix: netip.MustParsePrefix("::1/128"),
+ }},
+ CacheSize: 4 * 1024 * 1024,
EDNSClientSubnet: &dnsforward.EDNSClientSubnet{
CustomIP: netip.Addr{},
@@ -434,7 +439,7 @@ var config = &configuration{
MaxAge: 3,
},
OSConfig: &osConfig{},
- SchemaVersion: confmigrate.LastSchemaVersion,
+ SchemaVersion: configmigrate.LastSchemaVersion,
Theme: ThemeAuto,
}
@@ -479,14 +484,14 @@ func parseConfig() (err error) {
return err
}
- migrator := confmigrate.New(&confmigrate.Config{
+ migrator := configmigrate.New(&configmigrate.Config{
WorkingDir: Context.workDir,
})
var upgraded bool
config.fileData, upgraded, err = migrator.Migrate(
config.fileData,
- confmigrate.LastSchemaVersion,
+ configmigrate.LastSchemaVersion,
)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
diff --git a/internal/home/dns.go b/internal/home/dns.go
index 59d3fa62..5d601694 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -127,16 +127,11 @@ func initDNSServer(
httpReg aghhttp.RegisterFunc,
tlsConf *tlsConfigSettings,
) (err error) {
- privateNets, err := parseSubnetSet(config.DNS.PrivateNets)
- if err != nil {
- return fmt.Errorf("preparing set of private subnets: %w", err)
- }
-
Context.dnsServer, err = dnsforward.NewServer(dnsforward.DNSCreateParams{
DNSFilter: filters,
Stats: sts,
QueryLog: qlog,
- PrivateNets: privateNets,
+ PrivateNets: parseSubnetSet(config.DNS.PrivateNets),
Anonymizer: anonymizer,
DHCPServer: dhcpSrv,
EtcHosts: Context.etcHosts,
@@ -169,26 +164,15 @@ func initDNSServer(
// parseSubnetSet parses a slice of subnets. If the slice is empty, it returns
// a subnet set that matches all locally served networks, see
// [netutil.IsLocallyServed].
-func parseSubnetSet(nets []string) (s netutil.SubnetSet, err error) {
+func parseSubnetSet(nets []netutil.Prefix) (s netutil.SubnetSet) {
switch len(nets) {
case 0:
// Use an optimized function-based matcher.
- return netutil.SubnetSetFunc(netutil.IsLocallyServed), nil
+ return netutil.SubnetSetFunc(netutil.IsLocallyServed)
case 1:
- s, err = netutil.ParseSubnet(nets[0])
- if err != nil {
- return nil, err
- }
-
- return s, nil
+ return nets[0].Prefix
default:
- var nets []*net.IPNet
- nets, err = netutil.ParseSubnets(config.DNS.PrivateNets...)
- if err != nil {
- return nil, err
- }
-
- return netutil.SliceSubnetSet(nets), nil
+ return netutil.SliceSubnetSet(netutil.UnembedPrefixes(nets))
}
}
@@ -411,9 +395,9 @@ func applyAdditionalFiltering(clientIP netip.Addr, clientID string, setts *filte
setts.ClientIP = clientIP
- c, ok := Context.clients.Find(clientID)
+ c, ok := Context.clients.find(clientID)
if !ok {
- c, ok = Context.clients.Find(clientIP.String())
+ c, ok = Context.clients.find(clientIP.String())
if !ok {
log.Debug("%s: no clients with ip %s and clientid %q", pref, clientIP, clientID)
diff --git a/internal/home/dns_internal_test.go b/internal/home/dns_internal_test.go
index 22fa06fe..820b22a6 100644
--- a/internal/home/dns_internal_test.go
+++ b/internal/home/dns_internal_test.go
@@ -22,7 +22,7 @@ func TestApplyAdditionalFiltering(t *testing.T) {
}, nil)
require.NoError(t, err)
- Context.clients.idIndex = map[string]*Client{
+ Context.clients.idIndex = map[string]*persistentClient{
"default": {
UseOwnSettings: false,
safeSearchConf: filtering.SafeSearchConfig{Enabled: false},
@@ -108,7 +108,7 @@ func TestApplyAdditionalFiltering_blockedServices(t *testing.T) {
}, nil)
require.NoError(t, err)
- Context.clients.idIndex = map[string]*Client{
+ Context.clients.idIndex = map[string]*persistentClient{
"default": {
UseOwnBlockedServices: false,
},
diff --git a/internal/home/home.go b/internal/home/home.go
index ab2d83a2..701e4820 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -35,8 +35,10 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/osutil"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/slices"
)
@@ -231,11 +233,12 @@ func setupHostsContainer() (err error) {
return fmt.Errorf("initing hosts watcher: %w", err)
}
- Context.etcHosts, err = aghnet.NewHostsContainer(
- aghos.RootDirFS(),
- hostsWatcher,
- aghnet.DefaultHostsPaths()...,
- )
+ paths, err := hostsfile.DefaultHostsPaths()
+ if err != nil {
+ return fmt.Errorf("getting default system hosts paths: %w", err)
+ }
+
+ Context.etcHosts, err = aghnet.NewHostsContainer(osutil.RootDirFS(), hostsWatcher, paths...)
if err != nil {
closeErr := hostsWatcher.Close()
if errors.Is(err, aghnet.ErrNoHostsPaths) {
@@ -357,6 +360,11 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) {
)
conf.EtcHosts = Context.etcHosts
+ // TODO(s.chzhen): Use empty interface.
+ if Context.etcHosts == nil {
+ conf.EtcHosts = nil
+ }
+
conf.ConfigModified = onConfigModified
conf.HTTPRegister = httpRegister
conf.DataDir = Context.getDataDir()
@@ -605,7 +613,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
Context.auth, err = initUsers()
fatalOnError(err)
- Context.tls, err = newTLSManager(config.TLS)
+ Context.tls, err = newTLSManager(config.TLS, config.DNS.ServePlainDNS)
if err != nil {
log.Error("initializing tls: %s", err)
onConfigModified()
diff --git a/internal/home/options.go b/internal/home/options.go
index 1bbf2752..d32aaa1c 100644
--- a/internal/home/options.go
+++ b/internal/home/options.go
@@ -7,6 +7,7 @@ import (
"strconv"
"strings"
+ "github.com/AdguardTeam/AdGuardHome/internal/configmigrate"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
@@ -308,7 +309,7 @@ var cmdLineOpts = []cmdLineOpt{{
effect: func(o options, exec string) (effect, error) {
return func() error {
if o.verbose {
- fmt.Println(version.Verbose())
+ fmt.Print(version.Verbose(configmigrate.LastSchemaVersion))
} else {
fmt.Println(version.Full())
}
diff --git a/internal/home/tls.go b/internal/home/tls.go
index 004e9412..e022d043 100644
--- a/internal/home/tls.go
+++ b/internal/home/tls.go
@@ -38,15 +38,19 @@ type tlsManager struct {
confLock sync.Mutex
conf tlsConfigSettings
+
+ // servePlainDNS defines if plain DNS is allowed for incoming requests.
+ servePlainDNS bool
}
// newTLSManager initializes the manager of TLS configuration. m is always
// non-nil while any returned error indicates that the TLS configuration isn't
// valid. Thus TLS may be initialized later, e.g. via the web UI.
-func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) {
+func newTLSManager(conf tlsConfigSettings, servePlainDNS bool) (m *tlsManager, err error) {
m = &tlsManager{
- status: &tlsConfigStatus{},
- conf: conf,
+ status: &tlsConfigStatus{},
+ conf: conf,
+ servePlainDNS: servePlainDNS,
}
if m.conf.Enabled {
@@ -283,21 +287,29 @@ type tlsConfig struct {
tlsConfigSettingsExt `json:",inline"`
}
-// tlsConfigSettingsExt is used to (un)marshal the PrivateKeySaved field to
-// ensure that clients don't send and receive previously saved private keys.
+// tlsConfigSettingsExt is used to (un)marshal PrivateKeySaved field and
+// ServePlainDNS field.
type tlsConfigSettingsExt struct {
tlsConfigSettings `json:",inline"`
// PrivateKeySaved is true if the private key is saved as a string and omit
- // key from answer.
- PrivateKeySaved bool `yaml:"-" json:"private_key_saved,inline"`
+ // key from answer. It is used to ensure that clients don't send and
+ // receive previously saved private keys.
+ PrivateKeySaved bool `yaml:"-" json:"private_key_saved"`
+
+ // ServePlainDNS defines if plain DNS is allowed for incoming requests. It
+ // is an [aghalg.NullBool] to be able to tell when it's set without using
+ // pointers.
+ ServePlainDNS aghalg.NullBool `yaml:"-" json:"serve_plain_dns"`
}
+// handleTLSStatus is the handler for the GET /control/tls/status HTTP API.
func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
m.confLock.Lock()
data := tlsConfig{
tlsConfigSettingsExt: tlsConfigSettingsExt{
tlsConfigSettings: m.conf,
+ ServePlainDNS: aghalg.BoolToNullBool(m.servePlainDNS),
},
tlsConfigStatus: m.status,
}
@@ -306,6 +318,7 @@ func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
marshalTLS(w, r, data)
}
+// handleTLSValidate is the handler for the POST /control/tls/validate HTTP API.
func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts, err := unmarshalTLS(r)
if err != nil {
@@ -318,30 +331,8 @@ func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts.PrivateKey = m.conf.PrivateKey
}
- if setts.Enabled {
- err = validatePorts(
- tcpPort(config.HTTPConfig.Address.Port()),
- tcpPort(setts.PortHTTPS),
- tcpPort(setts.PortDNSOverTLS),
- tcpPort(setts.PortDNSCrypt),
- udpPort(config.DNS.Port),
- udpPort(setts.PortDNSOverQUIC),
- )
- if err != nil {
- aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
-
- return
- }
- }
-
- if !webCheckPortAvailable(setts.PortHTTPS) {
- aghhttp.Error(
- r,
- w,
- http.StatusBadRequest,
- "port %d is not available, cannot enable HTTPS on it",
- setts.PortHTTPS,
- )
+ if err = validateTLSSettings(setts); err != nil {
+ aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
@@ -358,7 +349,12 @@ func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
marshalTLS(w, r, resp)
}
-func (m *tlsManager) setConfig(newConf tlsConfigSettings, status *tlsConfigStatus) (restartHTTPS bool) {
+// setConfig updates manager conf with the given one.
+func (m *tlsManager) setConfig(
+ newConf tlsConfigSettings,
+ status *tlsConfigStatus,
+ servePlain aghalg.NullBool,
+) (restartHTTPS bool) {
m.confLock.Lock()
defer m.confLock.Unlock()
@@ -390,9 +386,15 @@ func (m *tlsManager) setConfig(newConf tlsConfigSettings, status *tlsConfigStatu
m.conf.PrivateKeyData = newConf.PrivateKeyData
m.status = status
+ if servePlain != aghalg.NBNull {
+ m.servePlainDNS = servePlain == aghalg.NBTrue
+ }
+
return restartHTTPS
}
+// handleTLSConfigure is the handler for the POST /control/tls/configure HTTP
+// API.
func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil {
@@ -405,31 +407,8 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
req.PrivateKey = m.conf.PrivateKey
}
- if req.Enabled {
- err = validatePorts(
- tcpPort(config.HTTPConfig.Address.Port()),
- tcpPort(req.PortHTTPS),
- tcpPort(req.PortDNSOverTLS),
- tcpPort(req.PortDNSCrypt),
- udpPort(config.DNS.Port),
- udpPort(req.PortDNSOverQUIC),
- )
- if err != nil {
- aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
-
- return
- }
- }
-
- // TODO(e.burkov): Investigate and perhaps check other ports.
- if !webCheckPortAvailable(req.PortHTTPS) {
- aghhttp.Error(
- r,
- w,
- http.StatusBadRequest,
- "port %d is not available, cannot enable https on it",
- req.PortHTTPS,
- )
+ if err = validateTLSSettings(req); err != nil {
+ aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
@@ -447,8 +426,18 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
return
}
- restartHTTPS := m.setConfig(req.tlsConfigSettings, status)
+ restartHTTPS := m.setConfig(req.tlsConfigSettings, status, req.ServePlainDNS)
m.setCertFileTime()
+
+ if req.ServePlainDNS != aghalg.NBNull {
+ func() {
+ m.confLock.Lock()
+ defer m.confLock.Unlock()
+
+ config.DNS.ServePlainDNS = req.ServePlainDNS == aghalg.NBTrue
+ }()
+ }
+
onConfigModified()
err = reconfigureDNSServer()
@@ -479,6 +468,33 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
}
}
+// validateTLSSettings returns error if the setts are not valid.
+func validateTLSSettings(setts tlsConfigSettingsExt) (err error) {
+ if setts.Enabled {
+ err = validatePorts(
+ tcpPort(config.HTTPConfig.Address.Port()),
+ tcpPort(setts.PortHTTPS),
+ tcpPort(setts.PortDNSOverTLS),
+ tcpPort(setts.PortDNSCrypt),
+ udpPort(config.DNS.Port),
+ udpPort(setts.PortDNSOverQUIC),
+ )
+ if err != nil {
+ // Don't wrap the error since it's informative enough as is.
+ return err
+ }
+ } else if setts.ServePlainDNS == aghalg.NBFalse {
+ // TODO(a.garipov): Support full disabling of all DNS.
+ return errors.Error("plain DNS is required in case encryption protocols are disabled")
+ }
+
+ if !webCheckPortAvailable(setts.PortHTTPS) {
+ return fmt.Errorf("port %d is not available, cannot enable HTTPS on it", setts.PortHTTPS)
+ }
+
+ return nil
+}
+
// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
// DNS protocols.
func validatePorts(
diff --git a/internal/next/cmd/opt.go b/internal/next/cmd/opt.go
index fdbea2a0..5959af76 100644
--- a/internal/next/cmd/opt.go
+++ b/internal/next/cmd/opt.go
@@ -10,6 +10,7 @@ import (
"os"
"strings"
+ "github.com/AdguardTeam/AdGuardHome/internal/configmigrate"
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/log"
@@ -382,7 +383,7 @@ func processOptions(
if opts.version {
if opts.verbose {
- fmt.Println(version.Verbose())
+ fmt.Print(version.Verbose(configmigrate.LastSchemaVersion))
} else {
fmt.Printf("AdGuard Home %s\n", version.Version())
}
diff --git a/internal/next/dnssvc/dnssvc.go b/internal/next/dnssvc/dnssvc.go
index e0056dcb..68c2e7e7 100644
--- a/internal/next/dnssvc/dnssvc.go
+++ b/internal/next/dnssvc/dnssvc.go
@@ -110,7 +110,7 @@ func addressesToUpstreams(
// TODO(e.burkov): Add system hosts resolver here.
var bootstrap upstream.ParallelResolver
for _, r := range boots {
- bootstrap = append(bootstrap, r)
+ bootstrap = append(bootstrap, upstream.NewCachingResolver(r))
}
upstreams = make([]upstream.Upstream, len(upsStrs))
diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go
index ae058541..73ac7e6a 100644
--- a/internal/querylog/qlog.go
+++ b/internal/querylog/qlog.go
@@ -196,7 +196,7 @@ func newLogEntry(params *AddParams) (entry *logEntry) {
// Add implements the [QueryLog] interface for *queryLog.
func (l *queryLog) Add(params *AddParams) {
var isEnabled, fileIsEnabled bool
- var memSize int
+ var memSize uint
func() {
l.confMu.RLock()
defer l.confMu.RUnlock()
@@ -205,7 +205,7 @@ func (l *queryLog) Add(params *AddParams) {
memSize = l.conf.MemSize
}()
- if !isEnabled || memSize == 0 {
+ if !isEnabled {
return
}
@@ -230,6 +230,7 @@ func (l *queryLog) Add(params *AddParams) {
if !l.flushPending && fileIsEnabled && l.buffer.Len() >= memSize {
l.flushPending = true
+ // TODO(s.chzhen): Fix occasional rewrite of entires.
go func() {
flushErr := l.flushLogBuffer()
if flushErr != nil {
diff --git a/internal/querylog/qlogreader.go b/internal/querylog/qlogreader.go
index 7d4d84b6..a222323e 100644
--- a/internal/querylog/qlogreader.go
+++ b/internal/querylog/qlogreader.go
@@ -131,7 +131,6 @@ func (r *qLogReader) ReadNext() (string, error) {
// Set its position to the start right away.
_, err = q.SeekStart()
-
// This is unexpected, return an error right away.
if err != nil {
return "", err
diff --git a/internal/querylog/querylog.go b/internal/querylog/querylog.go
index b9292c7d..189f1f57 100644
--- a/internal/querylog/querylog.go
+++ b/internal/querylog/querylog.go
@@ -63,7 +63,7 @@ type Config struct {
// MemSize is the number of entries kept in a memory buffer before they are
// flushed to disk.
- MemSize int
+ MemSize uint
// Enabled tells if the query log is enabled.
Enabled bool
@@ -143,14 +143,17 @@ func newQueryLog(conf Config) (l *queryLog, err error) {
}
}
- if conf.MemSize < 0 {
- return nil, errors.Error("memory size must be greater or equal to zero")
+ memSize := conf.MemSize
+ if memSize == 0 {
+ // If query log is enabled, we still need to write entries to a file.
+ // And all writing goes through a buffer.
+ memSize = 1
}
l = &queryLog{
findClient: findClient,
- buffer: aghalg.NewRingBuffer[*logEntry](conf.MemSize),
+ buffer: aghalg.NewRingBuffer[*logEntry](memSize),
conf: &Config{},
confMu: &sync.RWMutex{},
diff --git a/internal/querylog/search.go b/internal/querylog/search.go
index b4fd3ffe..1a992e20 100644
--- a/internal/querylog/search.go
+++ b/internal/querylog/search.go
@@ -47,7 +47,14 @@ func (l *queryLog) client(clientID, ip string, cache clientCache) (c *Client, er
// searchMemory looks up log records which are currently in the in-memory
// buffer. It optionally uses the client cache, if provided. It also returns
// the total amount of records in the buffer at the moment of searching.
+// l.confMu is expected to be locked.
func (l *queryLog) searchMemory(params *searchParams, cache clientCache) (entries []*logEntry, total int) {
+ // We use this configuration check because a buffer can contain a single log
+ // record. See [newQueryLog].
+ if l.conf.MemSize == 0 {
+ return nil, 0
+ }
+
l.bufferLock.Lock()
defer l.bufferLock.Unlock()
@@ -73,11 +80,12 @@ func (l *queryLog) searchMemory(params *searchParams, cache clientCache) (entrie
return true
})
- return entries, l.buffer.Len()
+ return entries, int(l.buffer.Len())
}
-// search - searches log entries in the query log using specified parameters
-// returns the list of entries found + time of the oldest entry
+// search searches log entries in memory buffer and log file using specified
+// parameters and returns the list of entries found and the time of the oldest
+// entry. l.confMu is expected to be locked.
func (l *queryLog) search(params *searchParams) (entries []*logEntry, oldest time.Time) {
start := time.Now()
diff --git a/internal/stats/unit.go b/internal/stats/unit.go
index 84c6770b..478d9f66 100644
--- a/internal/stats/unit.go
+++ b/internal/stats/unit.go
@@ -484,7 +484,7 @@ func (s *StatsCtx) fillCollectedStats(data *StatsResp, units []*unitDB, curID ui
data.TimeUnits = timeUnitsHours
daysCount := size / 24
- if daysCount > 7 {
+ if daysCount >= 7 {
size = daysCount
data.TimeUnits = timeUnitsDays
}
@@ -510,6 +510,10 @@ func (s *StatsCtx) fillCollectedStats(data *StatsResp, units []*unitDB, curID ui
// fillCollectedStatsDaily fills data with collected daily statistics. units
// must contain data for the count of days.
+//
+// TODO(s.chzhen): Improve collection of statistics for frontend. Dashboard
+// cards should contain statistics for the whole interval without rounding to
+// days.
func (s *StatsCtx) fillCollectedStatsDaily(
data *StatsResp,
units []*unitDB,
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index 36e339fa..c6af873d 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -10,25 +10,25 @@ require (
github.com/kyoh86/looppointer v0.2.1
github.com/securego/gosec/v2 v2.18.2
github.com/uudashr/gocognit v1.1.2
- golang.org/x/tools v0.16.0
- golang.org/x/vuln v1.0.1
+ golang.org/x/tools v0.17.0
+ golang.org/x/vuln v1.0.3
honnef.co/go/tools v0.4.6
- mvdan.cc/gofumpt v0.5.0
- mvdan.cc/unparam v0.0.0-20230917202934-3ee2d22f45fb
+ mvdan.cc/gofumpt v0.6.0
+ mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
- github.com/ccojocar/zxcvbn-go v1.0.1 // indirect
+ github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
- github.com/google/uuid v1.4.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/kyoh86/nolint v0.0.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
- golang.org/x/exp/typeparams v0.0.0-20231127185646-65229373498e // indirect
+ golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/mod v0.14.0 // indirect
- golang.org/x/sync v0.5.0 // indirect
- golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/sync v0.6.0 // indirect
+ golang.org/x/sys v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index 82c69078..fbd0a6c9 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -1,9 +1,9 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/ccojocar/zxcvbn-go v1.0.1 h1:+sxrANSCj6CdadkcMnvde/GWU1vZiiXRbqYSCalV4/4=
-github.com/ccojocar/zxcvbn-go v1.0.1/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60=
+github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg=
+github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -15,8 +15,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
-github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
-github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
@@ -32,7 +32,7 @@ github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ew
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/securego/gosec/v2 v2.18.2 h1:DkDt3wCiOtAHf1XkiXZBhQ6m6mK/b9T/wD257R3/c+I=
github.com/securego/gosec/v2 v2.18.2/go.mod h1:xUuqSF6i0So56Y2wwohWAmB07EdBkUN6crbLlHwbyJs=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
@@ -49,8 +49,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
-golang.org/x/exp/typeparams v0.0.0-20231127185646-65229373498e h1:Iel2aGgaO80fSb1N54L7SE6XMeVvYy6caKt8u/5LvR8=
-golang.org/x/exp/typeparams v0.0.0-20231127185646-65229373498e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
+golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM=
+golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/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=
@@ -63,12 +63,12 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
-golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -79,8 +79,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
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-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
@@ -93,10 +93,10 @@ golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4X
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.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
-golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
-golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
-golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
-golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM=
+golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
+golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
+golang.org/x/vuln v1.0.3 h1:k2wzzWGpdntQzNsCOLTabCFk76oTe69BPwad5H52F4w=
+golang.org/x/vuln v1.0.3/go.mod h1:NbJdUQhX8jY++FtuhrXs2Eyx0yePo9pF7nPlIjo9aaQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -106,7 +106,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8=
honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
-mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
-mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
-mvdan.cc/unparam v0.0.0-20230917202934-3ee2d22f45fb h1:xiF91GJnDSbyPdiZB5d52N2VpZfGhjM4Ji75cjzuooQ=
-mvdan.cc/unparam v0.0.0-20230917202934-3ee2d22f45fb/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI=
+mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
+mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA=
+mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w=
+mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI=
diff --git a/internal/version/version.go b/internal/version/version.go
index b553d6f8..e14e08dd 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -96,23 +96,25 @@ func fmtModule(m *debug.Module) (formatted string) {
// Constants defining the headers of build information message.
const (
- vFmtAGHHdr = "AdGuard Home"
- vFmtVerHdr = "Version: "
- vFmtChanHdr = "Channel: "
- vFmtGoHdr = "Go version: "
- vFmtTimeHdr = "Commit time: "
- vFmtRaceHdr = "Race: "
- vFmtGOOSHdr = "GOOS: " + runtime.GOOS
- vFmtGOARCHHdr = "GOARCH: " + runtime.GOARCH
- vFmtGOARMHdr = "GOARM: "
- vFmtGOMIPSHdr = "GOMIPS: "
- vFmtDepsHdr = "Dependencies:"
+ vFmtAGHHdr = "AdGuard Home"
+ vFmtVerHdr = "Version: "
+ vFmtSchemaVerHdr = "Schema version: "
+ vFmtChanHdr = "Channel: "
+ vFmtGoHdr = "Go version: "
+ vFmtTimeHdr = "Commit time: "
+ vFmtRaceHdr = "Race: "
+ vFmtGOOSHdr = "GOOS: " + runtime.GOOS
+ vFmtGOARCHHdr = "GOARCH: " + runtime.GOARCH
+ vFmtGOARMHdr = "GOARM: "
+ vFmtGOMIPSHdr = "GOMIPS: "
+ vFmtDepsHdr = "Dependencies:"
)
// Verbose returns formatted build information. Output example:
//
// AdGuard Home
// Version: v0.105.3
+// Schema version: 27
// Channel: development
// Go version: go1.15.3
// Build time: 2021-03-30T16:26:08Z+0300
@@ -125,34 +127,31 @@ const (
// ...
//
// TODO(e.burkov): Make it write into passed io.Writer.
-func Verbose() (v string) {
+func Verbose(schemaVersion uint) (v string) {
b := &strings.Builder{}
const nl = "\n"
- stringutil.WriteToBuilder(
- b,
- vFmtAGHHdr,
- nl,
- vFmtVerHdr,
- version,
- nl,
- vFmtChanHdr,
- channel,
- nl,
- vFmtGoHdr,
- runtime.Version(),
- )
+ stringutil.WriteToBuilder(b, vFmtAGHHdr, nl)
+ stringutil.WriteToBuilder(b, vFmtVerHdr, version, nl)
+
+ schemaVerStr := strconv.FormatUint(uint64(schemaVersion), 10)
+ stringutil.WriteToBuilder(b, vFmtSchemaVerHdr, schemaVerStr, nl)
+
+ stringutil.WriteToBuilder(b, vFmtChanHdr, channel, nl)
+ stringutil.WriteToBuilder(b, vFmtGoHdr, runtime.Version(), nl)
writeCommitTime(b)
- stringutil.WriteToBuilder(b, nl, vFmtGOOSHdr, nl, vFmtGOARCHHdr)
+ stringutil.WriteToBuilder(b, vFmtGOOSHdr, nl)
+ stringutil.WriteToBuilder(b, vFmtGOARCHHdr, nl)
+
if goarm != "" {
- stringutil.WriteToBuilder(b, nl, vFmtGOARMHdr, "v", goarm)
+ stringutil.WriteToBuilder(b, vFmtGOARMHdr, "v", goarm, nl)
} else if gomips != "" {
- stringutil.WriteToBuilder(b, nl, vFmtGOMIPSHdr, gomips)
+ stringutil.WriteToBuilder(b, vFmtGOMIPSHdr, gomips, nl)
}
- stringutil.WriteToBuilder(b, nl, vFmtRaceHdr, strconv.FormatBool(isRace))
+ stringutil.WriteToBuilder(b, vFmtRaceHdr, strconv.FormatBool(isRace), nl)
info, ok := debug.ReadBuildInfo()
if !ok {
@@ -163,10 +162,10 @@ func Verbose() (v string) {
return b.String()
}
- stringutil.WriteToBuilder(b, nl, vFmtDepsHdr)
+ stringutil.WriteToBuilder(b, vFmtDepsHdr, nl)
for _, dep := range info.Deps {
if depStr := fmtModule(dep); depStr != "" {
- stringutil.WriteToBuilder(b, "\n\t", depStr)
+ stringutil.WriteToBuilder(b, "\t", depStr, nl)
}
}
@@ -180,8 +179,8 @@ func writeCommitTime(b *strings.Builder) {
commitTimeUnix, err := strconv.ParseInt(committime, 10, 64)
if err != nil {
- stringutil.WriteToBuilder(b, "\n", vFmtTimeHdr, fmt.Sprintf("parse error: %s", err))
+ stringutil.WriteToBuilder(b, vFmtTimeHdr, fmt.Sprintf("parse error: %s", err), "\n")
} else {
- stringutil.WriteToBuilder(b, "\n", vFmtTimeHdr, time.Unix(commitTimeUnix, 0).String())
+ stringutil.WriteToBuilder(b, vFmtTimeHdr, time.Unix(commitTimeUnix, 0).String(), "\n")
}
}
diff --git a/internal/whois/whois.go b/internal/whois/whois.go
index b2d20a80..37f1dec8 100644
--- a/internal/whois/whois.go
+++ b/internal/whois/whois.go
@@ -268,7 +268,7 @@ var _ Interface = (*Default)(nil)
// Process makes WHOIS request and returns WHOIS information or nil. changed
// indicates that Info was updated since last request.
func (w *Default) Process(ctx context.Context, ip netip.Addr) (wi *Info, changed bool) {
- if netutil.IsSpecialPurposeAddr(ip) {
+ if netutil.IsSpecialPurpose(ip) {
return nil, false
}
diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md
index 16e7ab0d..de87f6fa 100644
--- a/openapi/CHANGELOG.md
+++ b/openapi/CHANGELOG.md
@@ -4,8 +4,28 @@
## v0.108.0: API changes
+## v0.107.44: API changes
+
+### The field `"upstream_mode"` in `DNSConfig`
+
+* The field `"upstream_mode"` in `POST /control/dns_config` and
+ `GET /control/dns_info` now accepts `load_balance` value. Note that, the usage
+ of an empty string or field absence is considered to as deprecated and is not
+ recommended. Use `load_balance` instead.
+
+### Type correction in `Client`
+
+* Field `upstreams_cache_size` of object `Client` now correctly has type
+ `integer` instead of the previous incorrect type `boolean`.
+
## v0.107.42: API changes
+### The new field `"serve_plain_dns"` in `TlsConfig`
+
+* The new field `"serve_plain_dns"` in `POST /control/tls/configure`,
+ `POST /control/tls/validate` and `GET /control/tls/status` is true if plain
+ DNS is allowed for incoming requests.
+
### The new fields `"upstreams_cache_enabled"` and `"upstreams_cache_size"` in `Client` object
* The new field `"upstreams_cache_enabled"` in `GET /control/clients`,
@@ -146,7 +166,7 @@ has the following format:
* The new field `"top_upstreams_responses"` in `GET /control/stats` method
shows the total number of responses from each upstream.
-* The new field `"top_upstrems_avg_time"` in `GET /control/stats` method shows
+* The new field `"top_upstreams_avg_time"` in `GET /control/stats` method shows
the average processing time in seconds of requests from each upstream.
## v0.107.30: API changes
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index c105ee1d..300311c1 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -1524,10 +1524,15 @@
'cache_optimistic':
'type': 'boolean'
'upstream_mode':
+ 'type': 'string'
'enum':
- - ''
- - 'parallel'
- - 'fastest_addr'
+ - const: ''
+ deprecated: true
+ description: Use `load_balance` instead.
+ - const: 'fastest_addr'
+ - const: 'load_balance'
+ - const: 'parallel'
+ 'description': Upstream modes enumeration.
'use_private_ptr_resolvers':
'type': 'boolean'
'resolve_clients':
@@ -2463,6 +2468,11 @@
'example': true
'description': >
Set to true if both certificate and private key are correct.
+ 'serve_plain_dns':
+ 'type': 'boolean'
+ 'example': true
+ 'description': >
+ Set to true if plain DNS is allowed for incoming requests.
'NetInterface':
'type': 'object'
'description': 'Network interface info'
@@ -2704,7 +2714,7 @@
changed.
This behaviour can be changed in the future versions.
- 'type': 'boolean'
+ 'type': 'integer'
'ClientAuto':
'type': 'object'
'description': 'Auto-Client information'
diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh
index c04499a1..ce8718dc 100644
--- a/scripts/make/go-lint.sh
+++ b/scripts/make/go-lint.sh
@@ -206,18 +206,18 @@ run_linter gocognit --over='11'\
run_linter gocognit --over='10'\
./internal/aghalg/\
- ./internal/aghchan/\
./internal/aghhttp/\
./internal/aghrenameio/\
./internal/aghtest/\
./internal/arpdb/\
./internal/client/\
- ./internal/confmigrate/\
+ ./internal/configmigrate/\
./internal/dhcpsvc\
./internal/dnsforward/\
./internal/filtering/hashprefix/\
./internal/filtering/rulelist/\
./internal/filtering/safesearch/\
+ ./internal/ipset\
./internal/next/\
./internal/rdns/\
./internal/schedule/\
@@ -243,7 +243,6 @@ run_linter nilness ./...
# TODO(a.garipov): Enable for all.
run_linter fieldalignment \
./internal/aghalg/\
- ./internal/aghchan/\
./internal/aghhttp/\
./internal/aghos/\
./internal/aghrenameio/\
@@ -251,15 +250,17 @@ run_linter fieldalignment \
./internal/aghtls/\
./internal/arpdb/\
./internal/client/\
- ./internal/confmigrate/\
+ ./internal/configmigrate/\
./internal/dhcpsvc/\
./internal/filtering/hashprefix/\
./internal/filtering/rewrite/\
./internal/filtering/rulelist/\
./internal/filtering/safesearch/\
+ ./internal/ipset/\
./internal/next/...\
./internal/querylog/\
./internal/rdns/\
+ ./internal/schedule/\
./internal/stats/\
./internal/updater/\
./internal/version/\
@@ -279,7 +280,7 @@ run_linter gosec --quiet\
./internal/aghtest/\
./internal/arpdb/\
./internal/client/\
- ./internal/confmigrate/\
+ ./internal/configmigrate/\
./internal/dhcpd/\
./internal/dhcpsvc/\
./internal/dnsforward/\
@@ -287,6 +288,7 @@ run_linter gosec --quiet\
./internal/filtering/rewrite/\
./internal/filtering/rulelist/\
./internal/filtering/safesearch/\
+ ./internal/ipset/\
./internal/next/\
./internal/rdns/\
./internal/schedule/\