diff --git a/CHANGELOG.md b/CHANGELOG.md index 25decf78177..d4654787e38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ NOTE: Add new changes BELOW THIS COMMENT. - Two new HTTP APIs, `PUT /control/querylog/config/update` and `GET control/querylog/config`, which can be used to set and receive the statistics configuration. See openapi/openapi.yaml for the full description. +- The ability to set custom IP for EDNS Client Subnet by using the new + `dns.edns_client_subnet.use_custom` and `dns.edns_client_subnet.custom_ip` + fields ([#1472]). The UI changes are coming in the upcoming releases. - The ability to use `dnstype` rules in the disallowed domains list ([#5468]). This allows dropping requests based on their question types. @@ -38,9 +41,9 @@ NOTE: Add new changes BELOW THIS COMMENT. #### Configuration Changes -In this release, the schema version has changed from 16 to 17. +In this release, the schema version has changed from 16 to 18. -- Property `statistics.interval`, which in schema versions 16 and earlier used +- Property `statistics.interval`, which in schema versions 17 and earlier used to be an integer number of days, is now a string with a human-readable duration: @@ -57,7 +60,31 @@ In this release, the schema version has changed from 16 to 17. ``` To rollback this change, convert the property back into days and change the - `schema_version` back to `16`. + `schema_version` back to `17`. +- Property `edns_client_subnet`, which in schema versions 16 and earlier used + to be a part of the `dns` object, is now part of the `dns.edns_client_subnet` + object: + + ```yaml + # BEFORE: + 'dns': + # … + 'edns_client_subnet': false + + # AFTER: + 'dns': + # … + 'edns_client_subnet': + 'enabled': false + 'use_custom': false + 'custom_ip': '' + ``` + + To rollback this change, move the value of `dns.edns_client_subnet.enabled` + into the `dns.edns_client_subnet`, remove the fields + `dns.edns_client_subnet.enabled`, `dns.edns_client_subnet.use_custom`, + `dns.edns_client_subnet.custom_ip`, and change the `schema_version` back to + `16`. ### Deprecated @@ -78,17 +105,23 @@ In this release, the schema version has changed from 16 to 17. ### Fixed +- Various dark theme bugs ([#5439], [#5441], [#5442], [#5515]). - Automatic update on MIPS64 and little-endian 32-bit MIPS architectures ([#5270], [#5373]). - Requirements to domain names in domain-specific upstream configurations have been relaxed to meet those from [RFC 3696][rfc3696] ([#4884]). - Failing service installation via script on FreeBSD ([#5431]). +[#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472 [#4884]: https://github.com/AdguardTeam/AdGuardHome/issues/4884 [#5270]: https://github.com/AdguardTeam/AdGuardHome/issues/5270 [#5373]: https://github.com/AdguardTeam/AdGuardHome/issues/5373 [#5431]: https://github.com/AdguardTeam/AdGuardHome/issues/5431 +[#5439]: https://github.com/AdguardTeam/AdGuardHome/issues/5439 +[#5441]: https://github.com/AdguardTeam/AdGuardHome/issues/5441 +[#5442]: https://github.com/AdguardTeam/AdGuardHome/issues/5442 [#5468]: https://github.com/AdguardTeam/AdGuardHome/issues/5468 +[#5515]: https://github.com/AdguardTeam/AdGuardHome/issues/5515 [rfc3696]: https://datatracker.ietf.org/doc/html/rfc3696 @@ -172,6 +205,7 @@ In this release, the schema version has changed from 14 to 16. 'file_enabled': true 'interval': '2160h' 'size_memory': 1000 + 'ignored': [] ``` To rollback this change, rename and move properties back into the `dns` diff --git a/client/src/helpers/trackers/trackers.json b/client/src/helpers/trackers/trackers.json index 17a870e9207..5e9cbd9c36c 100644 --- a/client/src/helpers/trackers/trackers.json +++ b/client/src/helpers/trackers/trackers.json @@ -1,5 +1,5 @@ { - "timeUpdated": "2023-02-21T12:46:33.324Z", + "timeUpdated": "2023-03-01T10:05:51.445Z", "categories": { "0": "audio_video_player", "1": "comments", @@ -19225,11 +19225,39 @@ "url": "http://www.zypmedia.com/", "companyId": "zypmedia" }, - "slack": { - "name": "Slack", + "adguard_dns": { + "name": "AdGuard DNS", "categoryId": 8, - "url": "https://www.slack.com/", - "companyId": "salesforce", + "url": "https://adguard-dns.io/", + "companyId": "adguard", + "source": "AdGuard" + }, + "adguard_vpn": { + "name": "AdGuard VPN", + "categoryId": 8, + "url": "https://adguard-vpn.com/", + "companyId": "adguard", + "source": "AdGuard" + }, + "appcenter": { + "name": "Microsoft App Center", + "categoryId": 5, + "url": "https://appcenter.ms/", + "companyId": null, + "source": "AdGuard" + }, + "alibaba_cloud": { + "name": "Alibaba Cloud", + "categoryId": 10, + "url": "https://www.alibabacloud.com/", + "companyId": "alibaba", + "source": "AdGuard" + }, + "alibaba_ucbrowser": { + "name": "UC Browser", + "categoryId": 8, + "url": "https://ucweb.com/", + "companyId": "alibaba", "source": "AdGuard" }, "apple": { @@ -19246,11 +19274,39 @@ "companyId": "apple", "source": "AdGuard" }, - "facebook_audience": { - "name": "Facebook Audience Network", + "azure": { + "name": "Microsoft Azure", + "categoryId": 10, + "url": "https://azure.microsoft.com/", + "companyId": "microsoft", + "source": "AdGuard" + }, + "azure_blob_storage": { + "name": "Azure Blob Storage", + "categoryId": 8, + "url": "https://azure.microsoft.com/en-us/products/storage/blobs", + "companyId": "microsoft", + "source": "AdGuard" + }, + "bitwarden": { + "name": "Bitwarden", + "categoryId": 8, + "url": "https://bitwarden.com/", + "companyId": "bitwarden", + "source": "AdGuard" + }, + "branch": { + "name": "Branch.io", + "categoryId": 101, + "url": "https://branch.io/", + "companyId": "branch_metrics_inc", + "source": "AdGuard" + }, + "button": { + "name": "Button", "categoryId": 4, - "url": "https://www.facebook.com/business/products/audience-network", - "companyId": "meta", + "url": "https://www.usebutton.com/", + "companyId": null, "source": "AdGuard" }, "crashlytics": { @@ -19260,18 +19316,25 @@ "companyId": null, "source": "AdGuard" }, - "showrss": { - "name": "showRSS", - "categoryId": 8, - "url": "https://showrss.info/", - "companyId": "showrss", + "element": { + "name": "Element", + "categoryId": 7, + "url": "https://element.io/", + "companyId": "element", "source": "AdGuard" }, - "hockeyapp": { - "name": "HockeyApp", + "facebook_audience": { + "name": "Facebook Audience Network", + "categoryId": 4, + "url": "https://www.facebook.com/business/products/audience-network", + "companyId": "meta", + "source": "AdGuard" + }, + "firebase": { + "name": "Firebase", "categoryId": 101, - "url": "https://hockeyapp.net/", - "companyId": null, + "url": "https://firebase.google.com/", + "companyId": "google", "source": "AdGuard" }, "gmail": { @@ -19288,32 +19351,32 @@ "companyId": "google", "source": "AdGuard" }, - "firebase": { - "name": "Firebase", + "hockeyapp": { + "name": "HockeyApp", "categoryId": 101, - "url": "https://firebase.google.com/", - "companyId": "google", + "url": "https://hockeyapp.net/", + "companyId": null, "source": "AdGuard" }, - "yandex_appmetrica": { - "name": "Yandex AppMetrica", - "categoryId": 101, - "url": "https://appmetrica.yandex.com/", - "companyId": "yandex", + "kik": { + "name": "Kik", + "categoryId": 7, + "url": "https://kik.com/", + "companyId": "kik", "source": "AdGuard" }, - "branch": { - "name": "Branch.io", - "categoryId": 101, - "url": "https://branch.io/", - "companyId": "branch_metrics_inc", + "lets_encrypt": { + "name": "Let's Encrypt", + "categoryId": 5, + "url": "https://letsencrypt.org/", + "companyId": "lets_encrypt", "source": "AdGuard" }, - "telstra": { - "name": "Telstra", - "categoryId": 8, - "url": "https://www.telstra.com.au/", - "companyId": "telstra", + "matrix": { + "name": "Matrix", + "categoryId": 5, + "url": "https://matrix.org/", + "companyId": "matrix", "source": "AdGuard" }, "medialab": { @@ -19323,39 +19386,39 @@ "companyId": "medialab", "source": "AdGuard" }, - "qualcomm": { - "name": "Qualcomm", + "meganz": { + "name": "Mega Ltd.", "categoryId": 8, - "url": "https://www.qualcomm.com/", - "companyId": "qualcomm", + "url": "https://mega.io/", + "companyId": "meganz", "source": "AdGuard" }, - "solaredge": { - "name": "SolarEdge Technologies, Inc.", + "msedge": { + "name": "Microsoft Edge", "categoryId": 8, - "url": "https://www.solaredge.com/", - "companyId": "solaredge", + "url": "https://www.microsoft.com/en-us/edge", + "companyId": "microsoft", "source": "AdGuard" }, - "sectigo": { - "name": "Sectigo Limited", - "categoryId": 5, - "url": "https://www.solaredge.com/", - "companyId": "sectigo", + "mozilla": { + "name": "Mozilla Foundation", + "categoryId": 8, + "url": "https://www.mozilla.org/", + "companyId": "mozilla", "source": "AdGuard" }, - "element": { - "name": "Element", - "categoryId": 7, - "url": "https://element.io/", - "companyId": "element", + "notion": { + "name": "Notion", + "categoryId": 8, + "url": "https://www.notion.so/", + "companyId": "notion", "source": "AdGuard" }, - "oztam": { - "name": "OzTAM", - "categoryId": 8, - "url": "https://oztam.com.au/", - "companyId": "oztam", + "ntppool": { + "name": "Network Time Protocol", + "categoryId": 5, + "url": "https://ntp.org/", + "companyId": "ntppool", "source": "AdGuard" }, "oppo": { @@ -19372,67 +19435,81 @@ "companyId": "microsoft", "source": "AdGuard" }, - "appcenter": { - "name": "Microsoft App Center", - "categoryId": 5, - "url": "https://appcenter.ms/", - "companyId": null, + "oztam": { + "name": "OzTAM", + "categoryId": 8, + "url": "https://oztam.com.au/", + "companyId": "oztam", "source": "AdGuard" }, - "unity_ads": { - "name": "Unity Ads", - "categoryId": 4, - "url": "https://unity.com/solutions/mobile-business/monetize-your-game", - "companyId": null, + "plex": { + "name": "Plex", + "categoryId": 0, + "url": "https://www.plex.tv/", + "companyId": "plex", "source": "AdGuard" }, - "azure": { - "name": "Microsoft Azure", - "categoryId": 10, - "url": "https://azure.microsoft.com/", - "companyId": "microsoft", + "qualcomm": { + "name": "Qualcomm", + "categoryId": 8, + "url": "https://www.qualcomm.com/", + "companyId": "qualcomm", "source": "AdGuard" }, - "button": { - "name": "Button", - "categoryId": 4, - "url": "https://www.usebutton.com/", - "companyId": null, + "sectigo": { + "name": "Sectigo Limited", + "categoryId": 5, + "url": "https://www.solaredge.com/", + "companyId": "sectigo", "source": "AdGuard" }, - "lets_encrypt": { - "name": "Let's Encrypt", - "categoryId": 5, - "url": "https://letsencrypt.org/", - "companyId": "lets_encrypt", + "showrss": { + "name": "showRSS", + "categoryId": 8, + "url": "https://showrss.info/", + "companyId": "showrss", "source": "AdGuard" }, - "kik": { - "name": "Kik", - "categoryId": 7, - "url": "https://kik.com/", - "companyId": "kik", + "similarweb": { + "name": "SimilarWeb", + "categoryId": 6, + "url": "https://www.similarweb.com/", + "companyId": "similarweb", "source": "AdGuard" }, - "plex": { - "name": "Plex", - "categoryId": 0, - "url": "https://www.plex.tv/", - "companyId": "plex", + "slack": { + "name": "Slack", + "categoryId": 8, + "url": "https://www.slack.com/", + "companyId": "salesforce", "source": "AdGuard" }, - "matrix": { - "name": "Matrix", - "categoryId": 5, - "url": "https://matrix.org/", - "companyId": "matrix", + "solaredge": { + "name": "SolarEdge Technologies, Inc.", + "categoryId": 8, + "url": "https://www.solaredge.com/", + "companyId": "solaredge", "source": "AdGuard" }, - "ntppool": { - "name": "Network Time Protocol", - "categoryId": 5, - "url": "https://ntp.org/", - "companyId": "ntppool", + "telstra": { + "name": "Telstra", + "categoryId": 8, + "url": "https://www.telstra.com.au/", + "companyId": "telstra", + "source": "AdGuard" + }, + "unity_ads": { + "name": "Unity Ads", + "categoryId": 4, + "url": "https://unity.com/solutions/mobile-business/monetize-your-game", + "companyId": null, + "source": "AdGuard" + }, + "vscode": { + "name": "Visual Studio Code", + "categoryId": 8, + "url": "https://code.visualstudio.com/", + "companyId": "microsoft", "source": "AdGuard" }, "whatsapp": { @@ -19442,19 +19519,40 @@ "companyId": "meta", "source": "AdGuard" }, - "vscode": { - "name": "Visual Studio Code", + "windows_maps": { + "name": "Windows Maps", "categoryId": 8, - "url": "https://code.visualstudio.com/", + "url": "https://www.microsoft.com/store/apps/9wzdncrdtbvb", "companyId": "microsoft", "source": "AdGuard" }, - "msedge": { - "name": "Microsoft Edge", + "windows_notifications": { + "name": "The Windows Push Notification Services", "categoryId": 8, - "url": "https://www.microsoft.com/en-us/edge", + "url": "https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/windows-push-notification-services--wns--overview", + "companyId": "microsoft", + "source": "AdGuard" + }, + "windows_time": { + "name": "Windows Time Service", + "categoryId": 8, + "url": "https://learn.microsoft.com/en-us/windows-server/networking/windows-time-service/how-the-windows-time-service-works", "companyId": "microsoft", "source": "AdGuard" + }, + "windowsupdate": { + "name": "Windows Update", + "categoryId": 9, + "url": "https://support.microsoft.com/en-us/windows/windows-update-faq-8a903416-6f45-0718-f5c7-375e92dddeb2", + "companyId": "microsoft", + "source": "AdGuard" + }, + "yandex_appmetrica": { + "name": "Yandex AppMetrica", + "categoryId": 101, + "url": "https://appmetrica.yandex.com/", + "companyId": "yandex", + "source": "AdGuard" } }, "trackerDomains": { @@ -23785,47 +23883,33 @@ "zwaar.net": "zwaar", "zwaar.org": "zwaar", "extend.tv": "zypmedia", - "whatsapp.net": "whatsapp", - "whatsapp.com": "whatsapp", - "telstra.com.au": "telstra", - "telstra.com": "telstra", - "slack.com": "slack", - "slackb.com": "slack", - "slack-edge.com": "slack", - "slack-imgs.com": "slack", - "addlive.io": "snap", - "feelinsonice.com": "snap", - "sc-cdn.net": "snap", - "sc-corp.net": "snap", - "sc-gw.com": "snap", - "sc-jpl.com": "snap", - "sc-prod.net": "snap", - "snap-dev.net": "snap", - "snapads.com": "snap", - "snapkit.com": "snap", + "adtidy.org": "adguard", + "agrd.io": "adguard", "adguard.app": "adguard", "adguard.io": "adguard", "adguard.org": "adguard", - "adguard-dns.com": "adguard", - "adguard-dns.io": "adguard", - "adguard-vpn.com": "adguard", - "adguardvpn.com": "adguard", - "adguard-vpn.online": "adguard", - "oppomobile.com": "oppo", - "heytapmobi.com": "oppo", - "heytapmobile.com": "oppo", - "heytapdl.com": "oppo", - "allawnos.com": "oppo", - "allawntech.com": "oppo", - "nflximg.com": "netflix", - "element.io": "element", - "riot.im": "element", - "gvt1.com": "google_servers", - "gvt2.com": "google_servers", - "gvt3.com": "google_servers", + "adguard-dns.com": "adguard_dns", + "adguard-dns.io": "adguard_dns", + "adguardvpn.com": "adguard_vpn", + "adguard-vpn.com": "adguard_vpn", + "adguard-vpn.online": "adguard_vpn", "akadns.net": "akamai_technologies", "akamaiedge.net": "akamai_technologies", "akaquill.net": "akamai_technologies", + "aliapp.org": "alibaba.com", + "alibabachengdun.com": "alibaba.com", + "alibabausercontent.com": "alibaba.com", + "aliexpress.com": "alibaba.com", + "alikunlun.com": "alibaba.com", + "aliyuncs.com": "alibaba.com", + "alibabacloud.com": "alibaba_cloud", + "alibabadns.com": "alibaba_cloud", + "aliyun.com": "alibaba_cloud", + "ucweb.com": "alibaba_ucbrowser", + "alipayobjects.com": "alipay.com", + "taobao.com": "taobao", + "appcenter.ms": "appcenter", + "iadsdk.apple.com": "apple_ads", "me.com": "apple", "apple.news": "apple", "apple-dns.net": "apple", @@ -23833,9 +23917,6 @@ "icloud.com": "apple", "itunes.com": "apple", "icloud-content.com": "apple", - "kik.com": "kik", - "apikik.com": "kik", - "kik-live.com": "kik", "mzstatic.com": "apple", "cdn-apple.com": "apple", "apple-mapkit.com": "apple", @@ -23845,46 +23926,103 @@ "apple-livephotoskit.com": "apple", "safebrowsing.apple": "apple", "safebrowsing.g.applimg.com": "apple", - "matrix.org": "matrix", - "medialab.la": "medialab", - "media-lab.ai": "medialab", - "phicdn.net": "digicert_trust_seal", - "e-msedge.net": "msedge", - "l-msedge.net": "msedge", - "exp-tas.com": "vscode", - "vscode-unpkg.net": "vscode", - "v0cdn.net": "vscode", - "vscode-cdn.net": "vscode", - "iadsdk.apple.com": "apple_ads", - "showrss.info": "showrss", - "sectigo.com": "sectigo", - "solaredge.com": "solaredge", - "crashlytics.com": "crashlytics", + "blob.core.windows.net": "azure_blob_storage", + "azure.com": "azure", + "trafficmanager.net": "azure", + "bitwarden.com": "bitwarden", + "mobileapptracking.com": "branch", + "bttn.io": "button", "cloudflare-dns.com": "cloudflare", + "crashlytics.com": "crashlytics", + "phicdn.net": "digicert_trust_seal", + "element.io": "element", + "riot.im": "element", + "app-measurement.com": "firebase", + "flipboard.com": "flipboard", "flurry.com": "flurry", + "gmail.com": "gmail", + "gvt1.com": "google_servers", + "gvt2.com": "google_servers", + "gvt3.com": "google_servers", + "pki.goog": "google_trust_services", "hockeyapp.net": "hockeyapp", - "app-measurement.com": "firebase", - "appmetrica.yandex.com": "yandex_appmetrica", + "kik.com": "kik", + "apikik.com": "kik", + "kik-live.com": "kik", "letsencrypt.org": "lets_encrypt", + "slatic.net": "lazada", "lencr.org": "lets_encrypt", - "oztam.com.au": "oztam", - "mobileapptracking.com": "branch", + "edgecastcdn.net": "markmonitor", + "matrix.org": "matrix", + "medialab.la": "medialab", + "media-lab.ai": "medialab", + "mega.co.nz": "meganz", + "mega.io": "meganz", + "mega.nz": "meganz", + "e-msedge.net": "msedge", + "l-msedge.net": "msedge", + "firefox.com": "mozilla", + "mozaws.net": "mozilla", + "mozgcp.net": "mozilla", + "mozilla.com": "mozilla", + "mozilla.net": "mozilla", + "mozilla.org": "mozilla", + "nflximg.com": "netflix", + "notion.so": "notion", "ntp.org": "ntppool", "ntppool.org": "ntppool", - "plex.tv": "plex", - "plex.direct": "plex", - "edgecastcdn.net": "markmonitor", - "appcenter.ms": "appcenter", - "unityads.unity3d.com": "unity_ads", - "usertrust.com": "trustlogo", - "azure.com": "azure", - "trafficmanager.net": "azure", + "oppomobile.com": "oppo", + "heytapmobi.com": "oppo", + "heytapmobile.com": "oppo", + "heytapdl.com": "oppo", + "allawnos.com": "oppo", + "allawntech.com": "oppo", "hotmail.com": "outlook", "outlook.com": "outlook", - "bttn.io": "button", - "pki.goog": "google_trust_services", + "oztam.com.au": "oztam", + "plex.tv": "plex", + "plex.direct": "plex", "xtracloud.net": "qualcomm", "qualcomm.com": "qualcomm", - "gmail.com": "gmail" + "sectigo.com": "sectigo", + "showrss.info": "showrss", + "similarweb.io": "similarweb", + "similarweb.com": "similarweb", + "slack.com": "slack", + "slackb.com": "slack", + "slack-edge.com": "slack", + "slack-imgs.com": "slack", + "addlive.io": "snap", + "feelinsonice.com": "snap", + "sc-cdn.net": "snap", + "sc-corp.net": "snap", + "sc-gw.com": "snap", + "sc-jpl.com": "snap", + "sc-prod.net": "snap", + "snap-dev.net": "snap", + "snapads.com": "snap", + "snapkit.com": "snap", + "solaredge.com": "solaredge", + "telstra.com.au": "telstra", + "telstra.com": "telstra", + "usertrust.com": "trustlogo", + "unityads.unity3d.com": "unity_ads", + "exp-tas.com": "vscode", + "vscode-unpkg.net": "vscode", + "v0cdn.net": "vscode", + "vscode-cdn.net": "vscode", + "whatsapp.net": "whatsapp", + "whatsapp.com": "whatsapp", + "maps.windows.com": "windows_maps", + "client.wns.windows.com": "windows_notifications", + "time.windows.com": "windows_time", + "windowsupdate.com": "windowsupdate", + "ya.ru": "yandex", + "yandex.by": "yandex", + "yandex.com": "yandex", + "yandex.com.tr": "yandex", + "yandex.fr": "yandex", + "yandex.kz": "yandex", + "appmetrica.yandex.com": "yandex_appmetrica" } } \ No newline at end of file diff --git a/internal/dhcpd/config.go b/internal/dhcpd/config.go index 8b255f76b9f..c942039aa3f 100644 --- a/internal/dhcpd/config.go +++ b/internal/dhcpd/config.go @@ -45,8 +45,10 @@ type DHCPServer interface { AddStaticLease(l *Lease) (err error) // RemoveStaticLease - remove a static lease RemoveStaticLease(l *Lease) (err error) - // FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases - FindMACbyIP(ip net.IP) net.HardwareAddr + + // FindMACbyIP returns a MAC address by the IP address of its lease, if + // there is one. + FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) // WriteDiskConfig4 - copy disk configuration WriteDiskConfig4(c *V4ServerConf) diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index 3602b4dba06..ef7fe19fd8d 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net" + "net/netip" "path/filepath" "time" @@ -42,7 +43,11 @@ type Lease struct { Hostname string `json:"hostname"` HWAddr net.HardwareAddr `json:"mac"` - IP net.IP `json:"ip"` + + // IP is the IP address leased to the client. + // + // TODO(a.garipov): Migrate leases.db and use netip.Addr. + IP net.IP `json:"ip"` } // Clone returns a deep copy of l. @@ -160,7 +165,7 @@ type Interface interface { Leases(flags GetLeasesFlags) (leases []*Lease) SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) - FindMACbyIP(ip net.IP) (mac net.HardwareAddr) + FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) WriteDiskConfig(c *ServerConfig) } @@ -174,7 +179,7 @@ type MockInterface struct { OnEnabled func() (ok bool) OnLeases func(flags GetLeasesFlags) (leases []*Lease) OnSetOnLeaseChanged func(f OnLeaseChangedT) - OnFindMACbyIP func(ip net.IP) (mac net.HardwareAddr) + OnFindMACbyIP func(ip netip.Addr) (mac net.HardwareAddr) OnWriteDiskConfig func(c *ServerConfig) } @@ -195,8 +200,10 @@ func (s *MockInterface) Leases(flags GetLeasesFlags) (ls []*Lease) { return s.On // SetOnLeaseChanged implements the Interface for *MockInterface. func (s *MockInterface) SetOnLeaseChanged(f OnLeaseChangedT) { s.OnSetOnLeaseChanged(f) } -// FindMACbyIP implements the Interface for *MockInterface. -func (s *MockInterface) FindMACbyIP(ip net.IP) (mac net.HardwareAddr) { return s.OnFindMACbyIP(ip) } +// FindMACbyIP implements the [Interface] for *MockInterface. +func (s *MockInterface) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) { + return s.OnFindMACbyIP(ip) +} // WriteDiskConfig implements the Interface for *MockInterface. func (s *MockInterface) WriteDiskConfig(c *ServerConfig) { s.OnWriteDiskConfig(c) } @@ -375,11 +382,13 @@ func (s *server) Leases(flags GetLeasesFlags) (leases []*Lease) { return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...) } -// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases -func (s *server) FindMACbyIP(ip net.IP) net.HardwareAddr { - if ip.To4() != nil { +// FindMACbyIP returns a MAC address by the IP address of its lease, if there is +// one. +func (s *server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) { + if ip.Is4() { return s.srv4.FindMACbyIP(ip) } + return s.srv6.FindMACbyIP(ip) } diff --git a/internal/dhcpd/v46_windows.go b/internal/dhcpd/v46_windows.go index 624ec767c2e..dcdb3caf1da 100644 --- a/internal/dhcpd/v46_windows.go +++ b/internal/dhcpd/v46_windows.go @@ -4,23 +4,26 @@ package dhcpd // 'u-root/u-root' package, a dependency of 'insomniacslk/dhcp' package, doesn't build on Windows -import "net" +import ( + "net" + "net/netip" +) type winServer struct{} // type check var _ DHCPServer = winServer{} -func (winServer) ResetLeases(_ []*Lease) (err error) { return nil } -func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil } -func (winServer) getLeasesRef() []*Lease { return nil } -func (winServer) AddStaticLease(_ *Lease) (err error) { return nil } -func (winServer) RemoveStaticLease(_ *Lease) (err error) { return nil } -func (winServer) FindMACbyIP(_ net.IP) (mac net.HardwareAddr) { return nil } -func (winServer) WriteDiskConfig4(_ *V4ServerConf) {} -func (winServer) WriteDiskConfig6(_ *V6ServerConf) {} -func (winServer) Start() (err error) { return nil } -func (winServer) Stop() (err error) { return nil } +func (winServer) ResetLeases(_ []*Lease) (err error) { return nil } +func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil } +func (winServer) getLeasesRef() []*Lease { return nil } +func (winServer) AddStaticLease(_ *Lease) (err error) { return nil } +func (winServer) RemoveStaticLease(_ *Lease) (err error) { return nil } +func (winServer) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil } +func (winServer) WriteDiskConfig4(_ *V4ServerConf) {} +func (winServer) WriteDiskConfig6(_ *V6ServerConf) {} +func (winServer) Start() (err error) { return nil } +func (winServer) Stop() (err error) { return nil } func v4Create(_ *V4ServerConf) (s DHCPServer, err error) { return winServer{}, nil } func v6Create(_ V6ServerConf) (s DHCPServer, err error) { return winServer{}, nil } diff --git a/internal/dhcpd/v4_unix.go b/internal/dhcpd/v4_unix.go index 64ebf25dd20..63ae46d9e87 100644 --- a/internal/dhcpd/v4_unix.go +++ b/internal/dhcpd/v4_unix.go @@ -200,20 +200,20 @@ func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) { return leases } -// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases -func (s *v4Server) FindMACbyIP(ip net.IP) net.HardwareAddr { +// FindMACbyIP implements the [Interface] for *v4Server. +func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) { now := time.Now() s.leasesLock.Lock() defer s.leasesLock.Unlock() - ip4 := ip.To4() - if ip4 == nil { + if !ip.Is4() { return nil } + netIP := ip.AsSlice() for _, l := range s.leases { - if l.IP.Equal(ip4) { + if l.IP.Equal(netIP) { if l.Expiry.After(now) || l.IsStatic() { return l.HWAddr } diff --git a/internal/dhcpd/v6_unix.go b/internal/dhcpd/v6_unix.go index 96512ddb707..d69d47c2cc4 100644 --- a/internal/dhcpd/v6_unix.go +++ b/internal/dhcpd/v6_unix.go @@ -6,6 +6,7 @@ import ( "bytes" "fmt" "net" + "net/netip" "sync" "time" @@ -107,21 +108,26 @@ func (s *v6Server) getLeasesRef() []*Lease { return s.leases } -// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases -func (s *v6Server) FindMACbyIP(ip net.IP) net.HardwareAddr { - now := time.Now().Unix() +// FindMACbyIP implements the [Interface] for *v6Server. +func (s *v6Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) { + now := time.Now() s.leasesLock.Lock() defer s.leasesLock.Unlock() + if !ip.Is6() { + return nil + } + + netIP := ip.AsSlice() for _, l := range s.leases { - if l.IP.Equal(ip) { - unix := l.Expiry.Unix() - if unix > now || unix == leaseExpireStatic { + if l.IP.Equal(netIP) { + if l.Expiry.After(now) || l.IsStatic() { return l.HWAddr } } } + return nil } diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index eeeb4c400bc..f18e751304b 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -53,7 +53,6 @@ const ( // The zero FilteringConfig is empty and ready for use. type FilteringConfig struct { // Callbacks for other modules - // -- // FilterHandler is an optional additional filtering callback. FilterHandler func(clientAddr net.IP, clientID string, settings *filtering.Settings) `yaml:"-"` @@ -64,50 +63,82 @@ type FilteringConfig struct { GetCustomUpstreamByClient func(id string) (conf *proxy.UpstreamConfig, err error) `yaml:"-"` // Protection configuration - // -- - ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of filtering features - BlockingMode BlockingMode `yaml:"blocking_mode"` // mode how to answer filtered requests - BlockingIPv4 net.IP `yaml:"blocking_ipv4"` // IP address to be returned for a blocked A request - BlockingIPv6 net.IP `yaml:"blocking_ipv6"` // IP address to be returned for a blocked AAAA request - BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600) + // ProtectionEnabled defines whether or not use any of filtering features. + ProtectionEnabled bool `yaml:"protection_enabled"` - // IP (or domain name) which is used to respond to DNS requests blocked by parental control or safe-browsing - ParentalBlockHost string `yaml:"parental_block_host"` + // BlockingMode defines the way how blocked responses are constructed. + BlockingMode BlockingMode `yaml:"blocking_mode"` + + // BlockingIPv4 is the IP address to be returned for a blocked A request. + BlockingIPv4 net.IP `yaml:"blocking_ipv4"` + + // BlockingIPv6 is the IP address to be returned for a blocked AAAA + // request. + BlockingIPv6 net.IP `yaml:"blocking_ipv6"` + + // BlockedResponseTTL is the time-to-live value for blocked responses. If + // 0, then default value is used (3600). + BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` + + // ParentalBlockHost is the IP (or domain name) which is used to respond to + // DNS requests blocked by parental control. + ParentalBlockHost string `yaml:"parental_block_host"` + + // SafeBrowsingBlockHost is the IP (or domain name) which is used to + // respond to DNS requests blocked by safe-browsing. SafeBrowsingBlockHost string `yaml:"safebrowsing_block_host"` // Anti-DNS amplification - // -- - Ratelimit uint32 `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable) - RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` // a list of whitelisted client IP addresses - RefuseAny bool `yaml:"refuse_any"` // if true, refuse ANY requests + // Ratelimit is the maximum number of requests per second from a given IP + // (0 to disable). + Ratelimit uint32 `yaml:"ratelimit"` + + // RatelimitWhitelist is the list of whitelisted client IP addresses. + RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` + + // RefuseAny, if true, refuse ANY requests. + RefuseAny bool `yaml:"refuse_any"` // Upstream DNS servers configuration - // -- - UpstreamDNS []string `yaml:"upstream_dns"` - UpstreamDNSFileName string `yaml:"upstream_dns_file"` - BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only) - AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled - FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm + // UpstreamDNS is the list of upstream DNS servers. + UpstreamDNS []string `yaml:"upstream_dns"` + + // UpstreamDNSFileName, if set, points to the file which contains upstream + // DNS servers. + UpstreamDNSFileName string `yaml:"upstream_dns_file"` + + // BootstrapDNS is the list of bootstrap DNS servers for DoH and DoT + // resolvers (plain DNS only). + BootstrapDNS []string `yaml:"bootstrap_dns"` + + // AllServers, if true, parallel queries to all configured upstream servers + // are enabled. + AllServers bool `yaml:"all_servers"` + + // FastestAddr, if true, use Fastest Address algorithm. + FastestAddr bool `yaml:"fastest_addr"` + // FastestTimeout replaces the default timeout for dialing IP addresses // when FastestAddr is true. FastestTimeout timeutil.Duration `yaml:"fastest_timeout"` // Access settings - // -- - // AllowedClients is the slice of IP addresses, CIDR networks, and ClientIDs - // of allowed clients. If not empty, only these clients are allowed, and - // [FilteringConfig.DisallowedClients] are ignored. + // AllowedClients is the slice of IP addresses, CIDR networks, and + // ClientIDs of allowed clients. If not empty, only these clients are + // allowed, and [FilteringConfig.DisallowedClients] are ignored. AllowedClients []string `yaml:"allowed_clients"` // DisallowedClients is the slice of IP addresses, CIDR networks, and // ClientIDs of disallowed clients. DisallowedClients []string `yaml:"disallowed_clients"` - BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked + // BlockedHosts is the list of hosts that should be blocked. + BlockedHosts []string `yaml:"blocked_hosts"` + // TrustedProxies is the list of IP addresses and CIDR networks to detect // proxy servers addresses the DoH requests from which should be handled. // The value of nil or an empty slice for this field makes Proxy not trust @@ -115,26 +146,46 @@ type FilteringConfig struct { TrustedProxies []string `yaml:"trusted_proxies"` // DNS cache settings - // -- - CacheSize uint32 `yaml:"cache_size"` // DNS cache size (in bytes) - CacheMinTTL uint32 `yaml:"cache_ttl_min"` // override TTL value (minimum) received from upstream server - CacheMaxTTL uint32 `yaml:"cache_ttl_max"` // override TTL value (maximum) received from upstream server + // CacheSize is the DNS cache size (in bytes). + CacheSize uint32 `yaml:"cache_size"` + + // CacheMinTTL is the override TTL value (minimum) received from upstream + // server. + CacheMinTTL uint32 `yaml:"cache_ttl_min"` + + // CacheMaxTTL is the override TTL value (maximum) received from upstream + // server. + CacheMaxTTL uint32 `yaml:"cache_ttl_max"` + // CacheOptimistic defines if optimistic cache mechanism should be used. CacheOptimistic bool `yaml:"cache_optimistic"` // Other settings - // -- - BogusNXDomain []string `yaml:"bogus_nxdomain"` // transform responses with these IP addresses to NXDOMAIN - AAAADisabled bool `yaml:"aaaa_disabled"` // Respond with an empty answer to all AAAA requests - EnableDNSSEC bool `yaml:"enable_dnssec"` // Set AD flag in outcoming DNS request - EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option - MaxGoroutines uint32 `yaml:"max_goroutines"` // Max. number of parallel goroutines for processing incoming requests - HandleDDR bool `yaml:"handle_ddr"` // Handle DDR requests + // BogusNXDomain is the list of IP addresses, responses with them will be + // transformed to NXDOMAIN. + BogusNXDomain []string `yaml:"bogus_nxdomain"` + + // AAAADisabled, if true, respond with an empty answer to all AAAA + // requests. + AAAADisabled bool `yaml:"aaaa_disabled"` + + // EnableDNSSEC, if true, set AD flag in outcoming DNS request. + EnableDNSSEC bool `yaml:"enable_dnssec"` + + // EDNSClientSubnet is the settings list for EDNS Client Subnet. + EDNSClientSubnet *EDNSClientSubnet `yaml:"edns_client_subnet"` - // IpsetList is the ipset configuration that allows AdGuard Home to add - // IP addresses of the specified domain names to an ipset list. Syntax: + // MaxGoroutines is the max number of parallel goroutines for processing + // incoming requests. + MaxGoroutines uint32 `yaml:"max_goroutines"` + + // HandleDDR, if true, handle DDR requests + HandleDDR bool `yaml:"handle_ddr"` + + // IpsetList is the ipset configuration that allows AdGuard Home to add IP + // addresses of the specified domain names to an ipset list. Syntax: // // DOMAIN[,DOMAIN].../IPSET_NAME // @@ -146,6 +197,18 @@ type FilteringConfig struct { IpsetListFileName string `yaml:"ipset_file"` } +// EDNSClientSubnet is the settings list for EDNS Client Subnet. +type EDNSClientSubnet struct { + // CustomIP for EDNS Client Subnet. + CustomIP string `yaml:"custom_ip"` + + // Enabled defines if EDNS Client Subnet is enabled. + Enabled bool `yaml:"enabled"` + + // UseCustom defines if CustomIP should be used. + UseCustom bool `yaml:"use_custom"` +} + // TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS type TLSConfig struct { cert tls.Certificate @@ -270,12 +333,24 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) { UpstreamConfig: srvConf.UpstreamConfig, BeforeRequestHandler: s.beforeRequestHandler, RequestHandler: s.handleDNSRequest, - EnableEDNSClientSubnet: srvConf.EnableEDNSClientSubnet, + EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled, MaxGoroutines: int(srvConf.MaxGoroutines), UseDNS64: srvConf.UseDNS64, DNS64Prefs: srvConf.DNS64Prefixes, } + if srvConf.EDNSClientSubnet.UseCustom { + // TODO(s.chzhen): Add wrapper around netip.Addr. + var ip net.IP + ip, err = netutil.ParseIP(srvConf.EDNSClientSubnet.CustomIP) + if err != nil { + return conf, fmt.Errorf("edns: %w", err) + } + + // TODO(s.chzhen): Use netip.Addr instead of net.IP inside dnsproxy. + conf.EDNSAddr = ip + } + if srvConf.CacheSize != 0 { conf.CacheEnabled = true conf.CacheSizeBytes = int(srvConf.CacheSize) diff --git a/internal/dnsforward/dns64_test.go b/internal/dnsforward/dns64_test.go index f1df9500b1c..85a07fc1dc3 100644 --- a/internal/dnsforward/dns64_test.go +++ b/internal/dnsforward/dns64_test.go @@ -287,6 +287,9 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, UseDNS64: true, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, localUps) t.Run(tc.name, func(t *testing.T) { diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go index d07c30dc525..02f1eb61a90 100644 --- a/internal/dnsforward/dns_test.go +++ b/internal/dnsforward/dns_test.go @@ -467,6 +467,11 @@ func TestServer_ProcessRestrictLocal(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + // TODO(s.chzhen): Add tests where EDNSClientSubnet.Enabled is true. + // Improve FilteringConfig declaration for tests. + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, ups) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ups} startDeferStop(t, s) @@ -539,6 +544,9 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) { ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) { return aghalg.Coalesce( diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 6d928422d7b..d0a0ada72b7 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -11,6 +11,7 @@ import ( "fmt" "math/big" "net" + "net/netip" "sync" "sync/atomic" "testing" @@ -155,6 +156,9 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte) s = createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, nil) tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem @@ -266,6 +270,9 @@ func TestServer(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, nil) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()} startDeferStop(t, s) @@ -304,7 +311,8 @@ func TestServer_timeout(t *testing.T) { srvConf := &ServerConfig{ UpstreamTimeout: timeout, FilteringConfig: FilteringConfig{ - BlockingMode: BlockingModeDefault, + BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, } @@ -322,6 +330,9 @@ func TestServer_timeout(t *testing.T) { require.NoError(t, err) s.conf.FilteringConfig.BlockingMode = BlockingModeDefault + s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{ + Enabled: false, + } err = s.Prepare(&s.conf) require.NoError(t, err) @@ -333,6 +344,9 @@ func TestServerWithProtectionDisabled(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, nil) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()} startDeferStop(t, s) @@ -437,6 +451,9 @@ func TestSafeSearch(t *testing.T) { TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, filterConf, forwardConf, nil) @@ -492,6 +509,11 @@ func TestInvalidRequest(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, + }, }, nil) startDeferStop(t, s) @@ -518,6 +540,9 @@ func TestBlockedRequest(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -543,6 +568,9 @@ func TestServerCustomClientUpstream(t *testing.T) { TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -591,6 +619,11 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, + }, }, nil) testUpstm := &aghtest.Upstream{ CName: testCNAMEs, @@ -621,6 +654,9 @@ func TestBlockCNAME(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -690,6 +726,9 @@ func TestClientRulesForCNAMEMatching(t *testing.T) { FilterHandler: func(_ net.IP, _ string, settings *filtering.Settings) { settings.FilteringEnabled = false }, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -731,6 +770,9 @@ func TestNullBlockedRequest(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeNullIP, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -783,6 +825,9 @@ func TestBlockedCustomIP(t *testing.T) { BlockingMode: BlockingModeCustomIP, BlockingIPv4: nil, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } @@ -831,6 +876,9 @@ func TestBlockedByHosts(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } @@ -864,6 +912,9 @@ func TestBlockedBySafeBrowsing(t *testing.T) { FilteringConfig: FilteringConfig{ SafeBrowsingBlockHost: ans4.String(), ProtectionEnabled: true, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, filterConf, forwardConf, nil) @@ -918,6 +969,9 @@ func TestRewrite(t *testing.T) { ProtectionEnabled: true, BlockingMode: BlockingModeDefault, UpstreamDNS: []string{"8.8.8.8:53"}, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, })) @@ -1009,7 +1063,7 @@ var testDHCP = &dhcpd.MockInterface{ }} }, OnSetOnLeaseChanged: func(olct dhcpd.OnLeaseChangedT) {}, - OnFindMACbyIP: func(ip net.IP) (mac net.HardwareAddr) { panic("not implemented") }, + OnFindMACbyIP: func(ip netip.Addr) (mac net.HardwareAddr) { panic("not implemented") }, OnWriteDiskConfig: func(c *dhcpd.ServerConfig) { panic("not implemented") }, } @@ -1032,6 +1086,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true s.conf.FilteringConfig.BlockingMode = BlockingModeDefault + s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} err = s.Prepare(&s.conf) require.NoError(t, err) @@ -1107,6 +1162,7 @@ func TestPTRResponseFromHosts(t *testing.T) { s.conf.TCPListenAddrs = []*net.TCPAddr{{}} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.BlockingMode = BlockingModeDefault + s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} err = s.Prepare(&s.conf) require.NoError(t, err) diff --git a/internal/dnsforward/filter_test.go b/internal/dnsforward/filter_test.go index 7fa0985a56e..3fbe58cc338 100644 --- a/internal/dnsforward/filter_test.go +++ b/internal/dnsforward/filter_test.go @@ -29,6 +29,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } filters := []filtering.Filter{{ diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index 18c4e82ec9a..a876a4114f4 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -57,7 +57,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) { blockingIPv4 := s.conf.BlockingIPv4 blockingIPv6 := s.conf.BlockingIPv6 ratelimit := s.conf.Ratelimit - enableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet + enableEDNSClientSubnet := s.conf.EDNSClientSubnet.Enabled enableDNSSEC := s.conf.EnableDNSSEC aaaaDisabled := s.conf.AAAADisabled cacheSize := s.conf.CacheSize @@ -280,7 +280,7 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) { setIfNotNil(&s.conf.LocalPTRResolvers, dc.LocalPTRUpstreams), setIfNotNil(&s.conf.UpstreamDNSFileName, dc.UpstreamsFile), setIfNotNil(&s.conf.BootstrapDNS, dc.Bootstraps), - setIfNotNil(&s.conf.EnableEDNSClientSubnet, dc.EDNSCSEnabled), + setIfNotNil(&s.conf.EDNSClientSubnet.Enabled, dc.EDNSCSEnabled), setIfNotNil(&s.conf.CacheSize, dc.CacheSize), setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL), setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL), diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index db3356dc90d..9d48151dd93 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -69,6 +69,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { ProtectionEnabled: true, BlockingMode: BlockingModeDefault, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, } @@ -144,6 +145,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { ProtectionEnabled: true, BlockingMode: BlockingModeDefault, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, } @@ -227,7 +229,10 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { require.True(t, ok) t.Run(tc.name, func(t *testing.T) { - t.Cleanup(func() { s.conf = defaultConf }) + t.Cleanup(func() { + s.conf = defaultConf + s.conf.FilteringConfig.EDNSClientSubnet.Enabled = false + }) rBody := io.NopCloser(bytes.NewReader(caseData.Req)) var r *http.Request @@ -443,6 +448,9 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, UpstreamTimeout: upsTimeout, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, nil) startDeferStop(t, srv) diff --git a/internal/filtering/servicelist.go b/internal/filtering/servicelist.go index e46b9383319..6e02536adcf 100644 --- a/internal/filtering/servicelist.go +++ b/internal/filtering/servicelist.go @@ -71,6 +71,7 @@ var blockedServices = []blockedService{{ "||amazon.jp^", "||amazon.nl^", "||amazon.red^", + "||amazon.se^", "||amazon.sg^", "||amazon^", "||amazonalexavoxcon.com^", @@ -1290,6 +1291,14 @@ var blockedServices = []blockedService{{ "||iq.com^", "||iqiyi.com^", }, +}, { + ID: "kakaotalk", + Name: "KakaoTalk", + IconSVG: []byte(""), + Rules: []string{ + "||kakao.com^", + "||kgslb.com^", + }, }, { ID: "leagueoflegends", Name: "League of Legends", @@ -1336,13 +1345,13 @@ var blockedServices = []blockedService{{ "||aus.social^", "||awscommunity.social^", "||cyberplace.social^", + "||defcon.social^", "||det.social^", "||fosstodon.org^", "||glasgow.social^", "||h4.io^", "||hachyderm.io^", "||hessen.social^", - "||home.social^", "||hostux.social^", "||ieji.de^", "||indieweb.social^", diff --git a/internal/home/client.go b/internal/home/client.go new file mode 100644 index 00000000000..d84b7c26163 --- /dev/null +++ b/internal/home/client.go @@ -0,0 +1,102 @@ +package home + +import ( + "encoding" + "fmt" + + "github.com/AdguardTeam/dnsproxy/proxy" +) + +// Client contains information about persistent clients. +type Client struct { + // upstreamConfig is the custom upstream config for this client. If + // it's nil, it has not been initialized yet. If it's non-nil and + // empty, there are no valid upstreams. If it's non-nil and non-empty, + // these upstream must be used. + upstreamConfig *proxy.UpstreamConfig + + Name string + + IDs []string + Tags []string + BlockedServices []string + Upstreams []string + + UseOwnSettings bool + FilteringEnabled bool + SafeSearchEnabled bool + SafeBrowsingEnabled bool + ParentalEnabled bool + UseOwnBlockedServices bool +} + +// closeUpstreams closes the client-specific upstream config of c if any. +func (c *Client) closeUpstreams() (err error) { + if c.upstreamConfig != nil { + err = c.upstreamConfig.Close() + if err != nil { + return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err) + } + } + + return nil +} + +// clientSource represents the source from which the information about the +// client has been obtained. +type clientSource uint + +// Clients information sources. The order determines the priority. +const ( + ClientSourceNone clientSource = iota + ClientSourceWHOIS + ClientSourceARP + ClientSourceRDNS + ClientSourceDHCP + ClientSourceHostsFile + ClientSourcePersistent +) + +// type check +var _ fmt.Stringer = clientSource(0) + +// String returns a human-readable name of cs. +func (cs clientSource) String() (s string) { + switch cs { + case ClientSourceWHOIS: + return "WHOIS" + case ClientSourceARP: + return "ARP" + case ClientSourceRDNS: + return "rDNS" + case ClientSourceDHCP: + return "DHCP" + case ClientSourceHostsFile: + return "etc/hosts" + default: + return "" + } +} + +// type check +var _ encoding.TextMarshaler = clientSource(0) + +// MarshalText implements encoding.TextMarshaler for the clientSource. +func (cs clientSource) MarshalText() (text []byte, err error) { + return []byte(cs.String()), nil +} + +// RuntimeClient is a client information about which has been obtained using the +// source described in the Source field. +type RuntimeClient struct { + WHOISInfo *RuntimeClientWHOISInfo + Host string + Source clientSource +} + +// RuntimeClientWHOISInfo is the filtered WHOIS data for a runtime client. +type RuntimeClientWHOISInfo struct { + City string `json:"city,omitempty"` + Country string `json:"country,omitempty"` + Orgname string `json:"orgname,omitempty"` +} diff --git a/internal/home/clients.go b/internal/home/clients.go index 2ae81377763..d5e0d02d251 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -2,7 +2,6 @@ package home import ( "bytes" - "encoding" "fmt" "net" "net/netip" @@ -25,122 +24,16 @@ import ( "golang.org/x/exp/slices" ) -const clientsUpdatePeriod = 10 * time.Minute - -var webHandlersRegistered = false - -// Client contains information about persistent clients. -type Client struct { - // upstreamConfig is the custom upstream config for this client. If - // it's nil, it has not been initialized yet. If it's non-nil and - // empty, there are no valid upstreams. If it's non-nil and non-empty, - // these upstream must be used. - upstreamConfig *proxy.UpstreamConfig - - Name string - - IDs []string - Tags []string - BlockedServices []string - Upstreams []string - - UseOwnSettings bool - FilteringEnabled bool - SafeSearchEnabled bool - SafeBrowsingEnabled bool - ParentalEnabled bool - UseOwnBlockedServices bool -} - -// closeUpstreams closes the client-specific upstream config of c if any. -func (c *Client) closeUpstreams() (err error) { - if c.upstreamConfig != nil { - err = c.upstreamConfig.Close() - if err != nil { - return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err) - } - } - - return nil -} - -type clientSource uint - -// Clients information sources. The order determines the priority. -const ( - ClientSourceNone clientSource = iota - ClientSourceWHOIS - ClientSourceARP - ClientSourceRDNS - ClientSourceDHCP - ClientSourceHostsFile - ClientSourcePersistent -) - -// type check -var _ fmt.Stringer = clientSource(0) - -// String returns a human-readable name of cs. -func (cs clientSource) String() (s string) { - switch cs { - case ClientSourceWHOIS: - return "WHOIS" - case ClientSourceARP: - return "ARP" - case ClientSourceRDNS: - return "rDNS" - case ClientSourceDHCP: - return "DHCP" - case ClientSourceHostsFile: - return "etc/hosts" - default: - return "" - } -} - -// type check -var _ encoding.TextMarshaler = clientSource(0) - -// MarshalText implements encoding.TextMarshaler for the clientSource. -func (cs clientSource) MarshalText() (text []byte, err error) { - return []byte(cs.String()), nil -} - -// clientSourceConf is used to configure where the runtime clients will be -// obtained from. -type clientSourcesConf struct { - WHOIS bool `yaml:"whois"` - ARP bool `yaml:"arp"` - RDNS bool `yaml:"rdns"` - DHCP bool `yaml:"dhcp"` - HostsFile bool `yaml:"hosts"` -} - -// RuntimeClient information -type RuntimeClient struct { - WHOISInfo *RuntimeClientWHOISInfo - Host string - Source clientSource -} - -// RuntimeClientWHOISInfo is the filtered WHOIS data for a runtime client. -type RuntimeClientWHOISInfo struct { - City string `json:"city,omitempty"` - Country string `json:"country,omitempty"` - Orgname string `json:"orgname,omitempty"` -} - +// clientsContainer is the storage of all runtime and persistent clients. type clientsContainer struct { - // TODO(a.garipov): Perhaps use a number of separate indices for - // different types (string, netip.Addr, and so on). + // TODO(a.garipov): Perhaps use a number of separate indices for different + // types (string, netip.Addr, and so on). list map[string]*Client // name -> client idIndex map[string]*Client // ID -> client // ipToRC is the IP address to *RuntimeClient map. ipToRC map[netip.Addr]*RuntimeClient - lock sync.Mutex - allTags *stringutil.Set // dhcpServer is used for looking up clients IP addresses by MAC addresses @@ -156,7 +49,16 @@ type clientsContainer struct { // arpdb stores the neighbors retrieved from ARP. arpdb aghnet.ARPDB - testing bool // if TRUE, this object is used for internal tests + // lock protects all fields. + // + // TODO(a.garipov): Use a pointer and describe which fields are protected in + // more detail. + lock sync.Mutex + + // testing is a flag that disables some features for internal tests. + // + // TODO(a.garipov): Awful. Remove. + testing bool } // Init initializes clients container @@ -202,24 +104,34 @@ func (clients *clientsContainer) handleHostsUpdates() { } } -// Start - start the module +// webHandlersRegistered prevents a [clientsContainer] from regisering its web +// handlers more than once. +// +// TODO(a.garipov): Refactor HTTP handler registration logic. +var webHandlersRegistered = false + +// Start starts the clients container. func (clients *clientsContainer) Start() { - if !clients.testing { - if !webHandlersRegistered { - webHandlersRegistered = true - clients.registerWebHandlers() - } - go clients.periodicUpdate() + if clients.testing { + return } + + if !webHandlersRegistered { + webHandlersRegistered = true + clients.registerWebHandlers() + } + + go clients.periodicUpdate() } -// Reload reloads runtime clients. -func (clients *clientsContainer) Reload() { +// reloadARP reloads runtime clients from ARP, if configured. +func (clients *clientsContainer) reloadARP() { if clients.arpdb != nil { clients.addFromSystemARP() } } +// clientObject is the YAML representation of a persistent client. type clientObject struct { Name string `yaml:"name"` @@ -317,12 +229,15 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) { return objs } +// arpClientsUpdatePeriod defines how often ARP clients are updated. +const arpClientsUpdatePeriod = 10 * time.Minute + func (clients *clientsContainer) periodicUpdate() { defer log.OnPanic("clients container") for { - clients.Reload() - time.Sleep(clientsUpdatePeriod) + clients.reloadARP() + time.Sleep(arpClientsUpdatePeriod) } } @@ -485,7 +400,8 @@ func (clients *clientsContainer) findUpstreams( return conf, nil } -// findLocked searches for a client by its ID. For internal use only. +// findLocked searches for a client by its ID. clients.lock is expected to be +// locked. func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) { c, ok = clients.idIndex[id] if ok { @@ -499,13 +415,13 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) { for _, c = range clients.list { for _, id := range c.IDs { - var n netip.Prefix - n, err = netip.ParsePrefix(id) + var subnet netip.Prefix + subnet, err = netip.ParsePrefix(id) if err != nil { continue } - if n.Contains(ip) { + if subnet.Contains(ip) { return c, true } } @@ -515,20 +431,25 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) { return nil, false } - macFound := clients.dhcpServer.FindMACbyIP(ip.AsSlice()) - if macFound == nil { + return clients.findDHCP(ip) +} + +// findDHCP searches for a client by its MAC, if the DHCP server is active and +// there is such client. clients.lock is expected to be locked. +func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *Client, ok bool) { + foundMAC := clients.dhcpServer.FindMACbyIP(ip) + if foundMAC == nil { return nil, false } for _, c = range clients.list { for _, id := range c.IDs { - var mac net.HardwareAddr - mac, err = net.ParseMAC(id) + mac, err := net.ParseMAC(id) if err != nil { continue } - if bytes.Equal(mac, macFound) { + if bytes.Equal(mac, foundMAC) { return c, true } } @@ -565,24 +486,13 @@ func (clients *clientsContainer) check(c *Client) (err error) { } for i, id := range c.IDs { - // Normalize structured data. - var ( - ip netip.Addr - n netip.Prefix - mac net.HardwareAddr - ) - - if ip, err = netip.ParseAddr(id); err == nil { - c.IDs[i] = ip.String() - } else if n, err = netip.ParsePrefix(id); err == nil { - c.IDs[i] = n.String() - } else if mac, err = net.ParseMAC(id); err == nil { - c.IDs[i] = mac.String() - } else if err = dnsforward.ValidateClientID(id); err == nil { - c.IDs[i] = strings.ToLower(id) - } else { - return fmt.Errorf("invalid clientid at index %d: %q", i, id) + var norm string + norm, err = normalizeClientIdentifier(id) + if err != nil { + return fmt.Errorf("client at index %d: %w", i, err) } + + c.IDs[i] = norm } for _, t := range c.Tags { @@ -601,6 +511,35 @@ func (clients *clientsContainer) check(c *Client) (err error) { return nil } +// normalizeClientIdentifier returns a normalized version of idStr. If idStr +// cannot be normalized, it returns an error. +func normalizeClientIdentifier(idStr string) (norm string, err error) { + if idStr == "" { + return "", errors.Error("clientid is empty") + } + + var ip netip.Addr + if ip, err = netip.ParseAddr(idStr); err == nil { + return ip.String(), nil + } + + var subnet netip.Prefix + if subnet, err = netip.ParsePrefix(idStr); err == nil { + return subnet.String(), nil + } + + var mac net.HardwareAddr + if mac, err = net.ParseMAC(idStr); err == nil { + return mac.String(), nil + } + + if err = dnsforward.ValidateClientID(idStr); err == nil { + return strings.ToLower(idStr), nil + } + + return "", fmt.Errorf("bad client identifier %q", idStr) +} + // Add adds a new client object. ok is false if such client already exists or // if an error occurred. func (clients *clientsContainer) Add(c *Client) (ok bool, err error) { @@ -666,21 +605,6 @@ func (clients *clientsContainer) Del(name string) (ok bool) { return true } -// equalStringSlices returns true if the slices are equal. -func equalStringSlices(a, b []string) (ok bool) { - if len(a) != len(b) { - return false - } - - for i := range a { - if a[i] != b[i] { - return false - } - } - - return true -} - // Update updates a client by its name. func (clients *clientsContainer) Update(name string, c *Client) (err error) { err = clients.check(c) @@ -704,22 +628,11 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) { } } - // Second, check the IP index. - if !equalStringSlices(prev.IDs, c.IDs) { - for _, id := range c.IDs { - c2, ok2 := clients.idIndex[id] - if ok2 && c2 != prev { - return fmt.Errorf("another client uses the same id (%q): %q", id, c2.Name) - } - } - - // Update ID index. - for _, id := range prev.IDs { - delete(clients.idIndex, id) - } - for _, id := range c.IDs { - clients.idIndex[id] = prev - } + // Second, update the ID index. + err = clients.updateIDIndex(prev, c.IDs) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return err } // Update name index. @@ -739,6 +652,32 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) { return nil } +// updateIDIndex updates the ID index data for cli using the information from +// newIDs. +func (clients *clientsContainer) updateIDIndex(cli *Client, newIDs []string) (err error) { + if slices.Equal(cli.IDs, newIDs) { + return nil + } + + for _, id := range newIDs { + existing, ok := clients.idIndex[id] + if ok && existing != cli { + return fmt.Errorf("id %q is used by client with name %q", id, existing.Name) + } + } + + // Update the IDs in the index. + for _, id := range cli.IDs { + delete(clients.idIndex, id) + } + + for _, id := range newIDs { + clients.idIndex[id] = cli + } + + return nil +} + // setWHOISInfo sets the WHOIS information for a client. func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) { clients.lock.Lock() diff --git a/internal/home/config.go b/internal/home/config.go index 3ca02a4b3aa..60575d2d17c 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -75,11 +75,21 @@ type osConfig struct { type clientsConfig struct { // Sources defines the set of sources to fetch the runtime clients from. - Sources *clientSourcesConf `yaml:"runtime_sources"` + Sources *clientSourcesConfig `yaml:"runtime_sources"` // Persistent are the configured clients. Persistent []*clientObject `yaml:"persistent"` } +// clientSourceConfig is used to configure where the runtime clients will be +// obtained from. +type clientSourcesConfig struct { + WHOIS bool `yaml:"whois"` + ARP bool `yaml:"arp"` + RDNS bool `yaml:"rdns"` + DHCP bool `yaml:"dhcp"` + HostsFile bool `yaml:"hosts"` +} + // configuration is loaded from YAML // field ordering is important -- yaml fields will mirror ordering from here type configuration struct { @@ -334,7 +344,7 @@ var config = &configuration{ }, }, Clients: &clientsConfig{ - Sources: &clientSourcesConf{ + Sources: &clientSourcesConfig{ WHOIS: true, ARP: true, RDNS: true, diff --git a/internal/home/home.go b/internal/home/home.go index 2fb74b3c279..9c0351eb48c 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -125,7 +125,7 @@ func Main(clientBuildFS fs.FS) { log.Info("Received signal %q", sig) switch sig { case syscall.SIGHUP: - Context.clients.Reload() + Context.clients.reloadARP() Context.tls.reload() default: cleanup(context.Background()) diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go index ed06ae9198a..af3715abdf5 100644 --- a/internal/home/upgrade.go +++ b/internal/home/upgrade.go @@ -90,6 +90,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) { upgradeSchema14to15, upgradeSchema15to16, upgradeSchema16to17, + upgradeSchema17to18, } n := 0 @@ -793,7 +794,7 @@ func upgradeSchema13to14(diskConf yobj) (err error) { diskConf["clients"] = yobj{ "persistent": clientsVal, - "runtime_sources": &clientSourcesConf{ + "runtime_sources": &clientSourcesConfig{ WHOIS: true, ARP: true, RDNS: rdnsSrc, @@ -893,13 +894,13 @@ func upgradeSchema15to16(diskConf yobj) (err error) { "ignored": []any{}, } - k := "statistics_interval" - v, has := dns[k] + const field = "statistics_interval" + v, has := dns[field] if has { stats["enabled"] = v != 0 stats["interval"] = v } - delete(dns, k) + delete(dns, field) diskConf["statistics"] = stats @@ -909,15 +910,52 @@ func upgradeSchema15to16(diskConf yobj) (err error) { // upgradeSchema16to17 performs the following changes: // // # BEFORE: +// 'dns': +// 'edns_client_subnet': false +// +// # AFTER: +// 'dns': +// 'edns_client_subnet': +// 'enabled': false +// 'use_custom': false +// 'custom_ip': "" +func upgradeSchema16to17(diskConf yobj) (err error) { + log.Printf("Upgrade yaml: 16 to 17") + diskConf["schema_version"] = 17 + + dnsVal, ok := diskConf["dns"] + if !ok { + return nil + } + + dns, ok := dnsVal.(yobj) + if !ok { + return fmt.Errorf("unexpected type of dns: %T", dnsVal) + } + + const field = "edns_client_subnet" + + dns[field] = map[string]any{ + "enabled": dns[field] == true, + "use_custom": false, + "custom_ip": "", + } + + return nil +} + +// upgradeSchema17to18 performs the following changes: +// +// # BEFORE: // 'statistics': // 'interval': 1 // // # AFTER: // 'statistics': // 'interval': 24h -func upgradeSchema16to17(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 16 to 17") - diskConf["schema_version"] = 17 +func upgradeSchema17to18(diskConf yobj) (err error) { + log.Printf("Upgrade yaml: 17 to 18") + diskConf["schema_version"] = 18 statsVal, ok := diskConf["statistics"] if !ok { diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go index d3dc0735bba..9e0830c39d4 100644 --- a/internal/home/upgrade_test.go +++ b/internal/home/upgrade_test.go @@ -579,7 +579,7 @@ func TestUpgradeSchema13to14(t *testing.T) { // The clients field will be added anyway. "clients": yobj{ "persistent": yarr{}, - "runtime_sources": &clientSourcesConf{ + "runtime_sources": &clientSourcesConfig{ WHOIS: true, ARP: true, RDNS: false, @@ -597,7 +597,7 @@ func TestUpgradeSchema13to14(t *testing.T) { "schema_version": newSchemaVer, "clients": yobj{ "persistent": []*clientObject{testClient}, - "runtime_sources": &clientSourcesConf{ + "runtime_sources": &clientSourcesConfig{ WHOIS: true, ARP: true, RDNS: false, @@ -618,7 +618,7 @@ func TestUpgradeSchema13to14(t *testing.T) { "schema_version": newSchemaVer, "clients": yobj{ "persistent": []*clientObject{testClient}, - "runtime_sources": &clientSourcesConf{ + "runtime_sources": &clientSourcesConfig{ WHOIS: true, ARP: true, RDNS: true, @@ -749,6 +749,67 @@ func TestUpgradeSchema15to16(t *testing.T) { } func TestUpgradeSchema16to17(t *testing.T) { + const newSchemaVer = 17 + + defaultWantObj := yobj{ + "dns": map[string]any{ + "edns_client_subnet": map[string]any{ + "enabled": false, + "use_custom": false, + "custom_ip": "", + }, + }, + "schema_version": newSchemaVer, + } + + testCases := []struct { + in yobj + want yobj + name string + }{{ + in: yobj{ + "dns": map[string]any{ + "edns_client_subnet": false, + }, + }, + want: defaultWantObj, + name: "basic", + }, { + in: yobj{ + "dns": map[string]any{}, + }, + want: defaultWantObj, + name: "default_values", + }, { + in: yobj{ + "dns": map[string]any{ + "edns_client_subnet": true, + }, + }, + want: yobj{ + "dns": map[string]any{ + "edns_client_subnet": map[string]any{ + "enabled": true, + "use_custom": false, + "custom_ip": "", + }, + }, + "schema_version": newSchemaVer, + }, + name: "is_true", + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := upgradeSchema16to17(tc.in) + require.NoError(t, err) + + assert.Equal(t, tc.want, tc.in) + }) + } +} + +func TestUpgradeSchema17to18(t *testing.T) { testCases := []struct { ivl any want any @@ -771,10 +832,10 @@ func TestUpgradeSchema16to17(t *testing.T) { "statistics": yobj{ "interval": tc.ivl, }, - "schema_version": 16, + "schema_version": 17, } t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema16to17(conf) + err := upgradeSchema17to18(conf) if tc.wantErr != "" { require.Error(t, err) @@ -785,7 +846,7 @@ func TestUpgradeSchema16to17(t *testing.T) { } require.NoError(t, err) - require.Equal(t, conf["schema_version"], 17) + require.Equal(t, conf["schema_version"], 18) statsVal, ok := conf["statistics"] require.True(t, ok) @@ -803,13 +864,13 @@ func TestUpgradeSchema16to17(t *testing.T) { } t.Run("no_stats", func(t *testing.T) { - err := upgradeSchema16to17(yobj{}) + err := upgradeSchema17to18(yobj{}) assert.NoError(t, err) }) t.Run("bad_stats", func(t *testing.T) { - err := upgradeSchema16to17(yobj{ + err := upgradeSchema17to18(yobj{ "statistics": 0, }) @@ -821,7 +882,7 @@ func TestUpgradeSchema16to17(t *testing.T) { "statistics": yobj{}, } - err := upgradeSchema16to17(conf) + err := upgradeSchema17to18(conf) require.NoError(t, err) statsVal, ok := conf["statistics"] diff --git a/internal/updater/updater.go b/internal/updater/updater.go index 1fdd858e81c..a3680506094 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -315,7 +315,7 @@ func (u *Updater) clean() { // MaxPackageFileSize is a maximum package file length in bytes. The largest // package whose size is limited by this constant currently has the size of // approximately 9 MiB. -const MaxPackageFileSize = 32 * 10 * 1024 +const MaxPackageFileSize = 32 * 1024 * 1024 // Download package file and save it to disk func (u *Updater) downloadPackageFile() (err error) {