Merge branch 'master' into 4299-querylog-stats-api

This commit is contained in:
Stanislav Chzhen 2023-03-01 18:45:17 +03:00
commit e0cbfc1c40
22 changed files with 989 additions and 485 deletions

View File

@ -31,6 +31,9 @@ NOTE: Add new changes BELOW THIS COMMENT.
- Two new HTTP APIs, `PUT /control/querylog/config/update` and `GET - Two new HTTP APIs, `PUT /control/querylog/config/update` and `GET
control/querylog/config`, which can be used to set and receive the statistics control/querylog/config`, which can be used to set and receive the statistics
configuration. See openapi/openapi.yaml for the full description. 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]). - The ability to use `dnstype` rules in the disallowed domains list ([#5468]).
This allows dropping requests based on their question types. This allows dropping requests based on their question types.
@ -38,9 +41,9 @@ NOTE: Add new changes BELOW THIS COMMENT.
#### Configuration Changes #### 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 to be an integer number of days, is now a string with a human-readable
duration: 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 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 ### Deprecated
@ -78,17 +105,23 @@ In this release, the schema version has changed from 16 to 17.
### Fixed ### Fixed
- Various dark theme bugs ([#5439], [#5441], [#5442], [#5515]).
- Automatic update on MIPS64 and little-endian 32-bit MIPS architectures - Automatic update on MIPS64 and little-endian 32-bit MIPS architectures
([#5270], [#5373]). ([#5270], [#5373]).
- Requirements to domain names in domain-specific upstream configurations have - Requirements to domain names in domain-specific upstream configurations have
been relaxed to meet those from [RFC 3696][rfc3696] ([#4884]). been relaxed to meet those from [RFC 3696][rfc3696] ([#4884]).
- Failing service installation via script on FreeBSD ([#5431]). - Failing service installation via script on FreeBSD ([#5431]).
[#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472
[#4884]: https://github.com/AdguardTeam/AdGuardHome/issues/4884 [#4884]: https://github.com/AdguardTeam/AdGuardHome/issues/4884
[#5270]: https://github.com/AdguardTeam/AdGuardHome/issues/5270 [#5270]: https://github.com/AdguardTeam/AdGuardHome/issues/5270
[#5373]: https://github.com/AdguardTeam/AdGuardHome/issues/5373 [#5373]: https://github.com/AdguardTeam/AdGuardHome/issues/5373
[#5431]: https://github.com/AdguardTeam/AdGuardHome/issues/5431 [#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 [#5468]: https://github.com/AdguardTeam/AdGuardHome/issues/5468
[#5515]: https://github.com/AdguardTeam/AdGuardHome/issues/5515
[rfc3696]: https://datatracker.ietf.org/doc/html/rfc3696 [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 'file_enabled': true
'interval': '2160h' 'interval': '2160h'
'size_memory': 1000 'size_memory': 1000
'ignored': []
``` ```
To rollback this change, rename and move properties back into the `dns` To rollback this change, rename and move properties back into the `dns`

View File

@ -1,5 +1,5 @@
{ {
"timeUpdated": "2023-02-21T12:46:33.324Z", "timeUpdated": "2023-03-01T10:05:51.445Z",
"categories": { "categories": {
"0": "audio_video_player", "0": "audio_video_player",
"1": "comments", "1": "comments",
@ -19225,11 +19225,39 @@
"url": "http://www.zypmedia.com/", "url": "http://www.zypmedia.com/",
"companyId": "zypmedia" "companyId": "zypmedia"
}, },
"slack": { "adguard_dns": {
"name": "Slack", "name": "AdGuard DNS",
"categoryId": 8, "categoryId": 8,
"url": "https://www.slack.com/", "url": "https://adguard-dns.io/",
"companyId": "salesforce", "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" "source": "AdGuard"
}, },
"apple": { "apple": {
@ -19246,11 +19274,39 @@
"companyId": "apple", "companyId": "apple",
"source": "AdGuard" "source": "AdGuard"
}, },
"facebook_audience": { "azure": {
"name": "Facebook Audience Network", "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, "categoryId": 4,
"url": "https://www.facebook.com/business/products/audience-network", "url": "https://www.usebutton.com/",
"companyId": "meta", "companyId": null,
"source": "AdGuard" "source": "AdGuard"
}, },
"crashlytics": { "crashlytics": {
@ -19260,18 +19316,25 @@
"companyId": null, "companyId": null,
"source": "AdGuard" "source": "AdGuard"
}, },
"showrss": { "element": {
"name": "showRSS", "name": "Element",
"categoryId": 8, "categoryId": 7,
"url": "https://showrss.info/", "url": "https://element.io/",
"companyId": "showrss", "companyId": "element",
"source": "AdGuard" "source": "AdGuard"
}, },
"hockeyapp": { "facebook_audience": {
"name": "HockeyApp", "name": "Facebook Audience Network",
"categoryId": 4,
"url": "https://www.facebook.com/business/products/audience-network",
"companyId": "meta",
"source": "AdGuard"
},
"firebase": {
"name": "Firebase",
"categoryId": 101, "categoryId": 101,
"url": "https://hockeyapp.net/", "url": "https://firebase.google.com/",
"companyId": null, "companyId": "google",
"source": "AdGuard" "source": "AdGuard"
}, },
"gmail": { "gmail": {
@ -19288,32 +19351,32 @@
"companyId": "google", "companyId": "google",
"source": "AdGuard" "source": "AdGuard"
}, },
"firebase": { "hockeyapp": {
"name": "Firebase", "name": "HockeyApp",
"categoryId": 101, "categoryId": 101,
"url": "https://firebase.google.com/", "url": "https://hockeyapp.net/",
"companyId": "google", "companyId": null,
"source": "AdGuard" "source": "AdGuard"
}, },
"yandex_appmetrica": { "kik": {
"name": "Yandex AppMetrica", "name": "Kik",
"categoryId": 101, "categoryId": 7,
"url": "https://appmetrica.yandex.com/", "url": "https://kik.com/",
"companyId": "yandex", "companyId": "kik",
"source": "AdGuard" "source": "AdGuard"
}, },
"branch": { "lets_encrypt": {
"name": "Branch.io", "name": "Let's Encrypt",
"categoryId": 101, "categoryId": 5,
"url": "https://branch.io/", "url": "https://letsencrypt.org/",
"companyId": "branch_metrics_inc", "companyId": "lets_encrypt",
"source": "AdGuard" "source": "AdGuard"
}, },
"telstra": { "matrix": {
"name": "Telstra", "name": "Matrix",
"categoryId": 8, "categoryId": 5,
"url": "https://www.telstra.com.au/", "url": "https://matrix.org/",
"companyId": "telstra", "companyId": "matrix",
"source": "AdGuard" "source": "AdGuard"
}, },
"medialab": { "medialab": {
@ -19323,39 +19386,39 @@
"companyId": "medialab", "companyId": "medialab",
"source": "AdGuard" "source": "AdGuard"
}, },
"qualcomm": { "meganz": {
"name": "Qualcomm", "name": "Mega Ltd.",
"categoryId": 8, "categoryId": 8,
"url": "https://www.qualcomm.com/", "url": "https://mega.io/",
"companyId": "qualcomm", "companyId": "meganz",
"source": "AdGuard" "source": "AdGuard"
}, },
"solaredge": { "msedge": {
"name": "SolarEdge Technologies, Inc.", "name": "Microsoft Edge",
"categoryId": 8, "categoryId": 8,
"url": "https://www.solaredge.com/", "url": "https://www.microsoft.com/en-us/edge",
"companyId": "solaredge", "companyId": "microsoft",
"source": "AdGuard" "source": "AdGuard"
}, },
"sectigo": { "mozilla": {
"name": "Sectigo Limited", "name": "Mozilla Foundation",
"categoryId": 8,
"url": "https://www.mozilla.org/",
"companyId": "mozilla",
"source": "AdGuard"
},
"notion": {
"name": "Notion",
"categoryId": 8,
"url": "https://www.notion.so/",
"companyId": "notion",
"source": "AdGuard"
},
"ntppool": {
"name": "Network Time Protocol",
"categoryId": 5, "categoryId": 5,
"url": "https://www.solaredge.com/", "url": "https://ntp.org/",
"companyId": "sectigo", "companyId": "ntppool",
"source": "AdGuard"
},
"element": {
"name": "Element",
"categoryId": 7,
"url": "https://element.io/",
"companyId": "element",
"source": "AdGuard"
},
"oztam": {
"name": "OzTAM",
"categoryId": 8,
"url": "https://oztam.com.au/",
"companyId": "oztam",
"source": "AdGuard" "source": "AdGuard"
}, },
"oppo": { "oppo": {
@ -19372,46 +19435,11 @@
"companyId": "microsoft", "companyId": "microsoft",
"source": "AdGuard" "source": "AdGuard"
}, },
"appcenter": { "oztam": {
"name": "Microsoft App Center", "name": "OzTAM",
"categoryId": 5, "categoryId": 8,
"url": "https://appcenter.ms/", "url": "https://oztam.com.au/",
"companyId": null, "companyId": "oztam",
"source": "AdGuard"
},
"unity_ads": {
"name": "Unity Ads",
"categoryId": 4,
"url": "https://unity.com/solutions/mobile-business/monetize-your-game",
"companyId": null,
"source": "AdGuard"
},
"azure": {
"name": "Microsoft Azure",
"categoryId": 10,
"url": "https://azure.microsoft.com/",
"companyId": "microsoft",
"source": "AdGuard"
},
"button": {
"name": "Button",
"categoryId": 4,
"url": "https://www.usebutton.com/",
"companyId": null,
"source": "AdGuard"
},
"lets_encrypt": {
"name": "Let's Encrypt",
"categoryId": 5,
"url": "https://letsencrypt.org/",
"companyId": "lets_encrypt",
"source": "AdGuard"
},
"kik": {
"name": "Kik",
"categoryId": 7,
"url": "https://kik.com/",
"companyId": "kik",
"source": "AdGuard" "source": "AdGuard"
}, },
"plex": { "plex": {
@ -19421,25 +19449,60 @@
"companyId": "plex", "companyId": "plex",
"source": "AdGuard" "source": "AdGuard"
}, },
"matrix": { "qualcomm": {
"name": "Matrix", "name": "Qualcomm",
"categoryId": 5,
"url": "https://matrix.org/",
"companyId": "matrix",
"source": "AdGuard"
},
"ntppool": {
"name": "Network Time Protocol",
"categoryId": 5,
"url": "https://ntp.org/",
"companyId": "ntppool",
"source": "AdGuard"
},
"whatsapp": {
"name": "WhatsApp",
"categoryId": 8, "categoryId": 8,
"url": "https://www.whatsapp.com/", "url": "https://www.qualcomm.com/",
"companyId": "meta", "companyId": "qualcomm",
"source": "AdGuard"
},
"sectigo": {
"name": "Sectigo Limited",
"categoryId": 5,
"url": "https://www.solaredge.com/",
"companyId": "sectigo",
"source": "AdGuard"
},
"showrss": {
"name": "showRSS",
"categoryId": 8,
"url": "https://showrss.info/",
"companyId": "showrss",
"source": "AdGuard"
},
"similarweb": {
"name": "SimilarWeb",
"categoryId": 6,
"url": "https://www.similarweb.com/",
"companyId": "similarweb",
"source": "AdGuard"
},
"slack": {
"name": "Slack",
"categoryId": 8,
"url": "https://www.slack.com/",
"companyId": "salesforce",
"source": "AdGuard"
},
"solaredge": {
"name": "SolarEdge Technologies, Inc.",
"categoryId": 8,
"url": "https://www.solaredge.com/",
"companyId": "solaredge",
"source": "AdGuard"
},
"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" "source": "AdGuard"
}, },
"vscode": { "vscode": {
@ -19449,12 +19512,47 @@
"companyId": "microsoft", "companyId": "microsoft",
"source": "AdGuard" "source": "AdGuard"
}, },
"msedge": { "whatsapp": {
"name": "Microsoft Edge", "name": "WhatsApp",
"categoryId": 8, "categoryId": 8,
"url": "https://www.microsoft.com/en-us/edge", "url": "https://www.whatsapp.com/",
"companyId": "meta",
"source": "AdGuard"
},
"windows_maps": {
"name": "Windows Maps",
"categoryId": 8,
"url": "https://www.microsoft.com/store/apps/9wzdncrdtbvb",
"companyId": "microsoft", "companyId": "microsoft",
"source": "AdGuard" "source": "AdGuard"
},
"windows_notifications": {
"name": "The Windows Push Notification Services",
"categoryId": 8,
"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": { "trackerDomains": {
@ -23785,10 +23883,111 @@
"zwaar.net": "zwaar", "zwaar.net": "zwaar",
"zwaar.org": "zwaar", "zwaar.org": "zwaar",
"extend.tv": "zypmedia", "extend.tv": "zypmedia",
"whatsapp.net": "whatsapp", "adtidy.org": "adguard",
"whatsapp.com": "whatsapp", "agrd.io": "adguard",
"telstra.com.au": "telstra", "adguard.app": "adguard",
"telstra.com": "telstra", "adguard.io": "adguard",
"adguard.org": "adguard",
"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",
"aaplimg.com": "apple",
"icloud.com": "apple",
"itunes.com": "apple",
"icloud-content.com": "apple",
"mzstatic.com": "apple",
"cdn-apple.com": "apple",
"apple-mapkit.com": "apple",
"icons.axm-usercontent-apple.com": "apple",
"apple-cloudkit.com": "apple",
"apzones.com": "apple",
"apple-livephotoskit.com": "apple",
"safebrowsing.apple": "apple",
"safebrowsing.g.applimg.com": "apple",
"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",
"kik.com": "kik",
"apikik.com": "kik",
"kik-live.com": "kik",
"letsencrypt.org": "lets_encrypt",
"slatic.net": "lazada",
"lencr.org": "lets_encrypt",
"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",
"oppomobile.com": "oppo",
"heytapmobi.com": "oppo",
"heytapmobile.com": "oppo",
"heytapdl.com": "oppo",
"allawnos.com": "oppo",
"allawntech.com": "oppo",
"hotmail.com": "outlook",
"outlook.com": "outlook",
"oztam.com.au": "oztam",
"plex.tv": "plex",
"plex.direct": "plex",
"xtracloud.net": "qualcomm",
"qualcomm.com": "qualcomm",
"sectigo.com": "sectigo",
"showrss.info": "showrss",
"similarweb.io": "similarweb",
"similarweb.com": "similarweb",
"slack.com": "slack", "slack.com": "slack",
"slackb.com": "slack", "slackb.com": "slack",
"slack-edge.com": "slack", "slack-edge.com": "slack",
@ -23803,88 +24002,27 @@
"snap-dev.net": "snap", "snap-dev.net": "snap",
"snapads.com": "snap", "snapads.com": "snap",
"snapkit.com": "snap", "snapkit.com": "snap",
"adguard.app": "adguard", "solaredge.com": "solaredge",
"adguard.io": "adguard", "telstra.com.au": "telstra",
"adguard.org": "adguard", "telstra.com": "telstra",
"adguard-dns.com": "adguard", "usertrust.com": "trustlogo",
"adguard-dns.io": "adguard", "unityads.unity3d.com": "unity_ads",
"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",
"akadns.net": "akamai_technologies",
"akamaiedge.net": "akamai_technologies",
"akaquill.net": "akamai_technologies",
"me.com": "apple",
"apple.news": "apple",
"apple-dns.net": "apple",
"aaplimg.com": "apple",
"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",
"icons.axm-usercontent-apple.com": "apple",
"apple-cloudkit.com": "apple",
"apzones.com": "apple",
"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", "exp-tas.com": "vscode",
"vscode-unpkg.net": "vscode", "vscode-unpkg.net": "vscode",
"v0cdn.net": "vscode", "v0cdn.net": "vscode",
"vscode-cdn.net": "vscode", "vscode-cdn.net": "vscode",
"iadsdk.apple.com": "apple_ads", "whatsapp.net": "whatsapp",
"showrss.info": "showrss", "whatsapp.com": "whatsapp",
"sectigo.com": "sectigo", "maps.windows.com": "windows_maps",
"solaredge.com": "solaredge", "client.wns.windows.com": "windows_notifications",
"crashlytics.com": "crashlytics", "time.windows.com": "windows_time",
"cloudflare-dns.com": "cloudflare", "windowsupdate.com": "windowsupdate",
"flurry.com": "flurry", "ya.ru": "yandex",
"hockeyapp.net": "hockeyapp", "yandex.by": "yandex",
"app-measurement.com": "firebase", "yandex.com": "yandex",
"appmetrica.yandex.com": "yandex_appmetrica", "yandex.com.tr": "yandex",
"letsencrypt.org": "lets_encrypt", "yandex.fr": "yandex",
"lencr.org": "lets_encrypt", "yandex.kz": "yandex",
"oztam.com.au": "oztam", "appmetrica.yandex.com": "yandex_appmetrica"
"mobileapptracking.com": "branch",
"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",
"hotmail.com": "outlook",
"outlook.com": "outlook",
"bttn.io": "button",
"pki.goog": "google_trust_services",
"xtracloud.net": "qualcomm",
"qualcomm.com": "qualcomm",
"gmail.com": "gmail"
} }
} }

View File

@ -45,8 +45,10 @@ type DHCPServer interface {
AddStaticLease(l *Lease) (err error) AddStaticLease(l *Lease) (err error)
// RemoveStaticLease - remove a static lease // RemoveStaticLease - remove a static lease
RemoveStaticLease(l *Lease) (err error) 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 - copy disk configuration
WriteDiskConfig4(c *V4ServerConf) WriteDiskConfig4(c *V4ServerConf)

View File

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
"net/netip"
"path/filepath" "path/filepath"
"time" "time"
@ -42,7 +43,11 @@ type Lease struct {
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
HWAddr net.HardwareAddr `json:"mac"` 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. // Clone returns a deep copy of l.
@ -160,7 +165,7 @@ type Interface interface {
Leases(flags GetLeasesFlags) (leases []*Lease) Leases(flags GetLeasesFlags) (leases []*Lease)
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
FindMACbyIP(ip net.IP) (mac net.HardwareAddr) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)
WriteDiskConfig(c *ServerConfig) WriteDiskConfig(c *ServerConfig)
} }
@ -174,7 +179,7 @@ type MockInterface struct {
OnEnabled func() (ok bool) OnEnabled func() (ok bool)
OnLeases func(flags GetLeasesFlags) (leases []*Lease) OnLeases func(flags GetLeasesFlags) (leases []*Lease)
OnSetOnLeaseChanged func(f OnLeaseChangedT) OnSetOnLeaseChanged func(f OnLeaseChangedT)
OnFindMACbyIP func(ip net.IP) (mac net.HardwareAddr) OnFindMACbyIP func(ip netip.Addr) (mac net.HardwareAddr)
OnWriteDiskConfig func(c *ServerConfig) 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. // SetOnLeaseChanged implements the Interface for *MockInterface.
func (s *MockInterface) SetOnLeaseChanged(f OnLeaseChangedT) { s.OnSetOnLeaseChanged(f) } func (s *MockInterface) SetOnLeaseChanged(f OnLeaseChangedT) { s.OnSetOnLeaseChanged(f) }
// FindMACbyIP implements the Interface for *MockInterface. // FindMACbyIP implements the [Interface] for *MockInterface.
func (s *MockInterface) FindMACbyIP(ip net.IP) (mac net.HardwareAddr) { return s.OnFindMACbyIP(ip) } func (s *MockInterface) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
return s.OnFindMACbyIP(ip)
}
// WriteDiskConfig implements the Interface for *MockInterface. // WriteDiskConfig implements the Interface for *MockInterface.
func (s *MockInterface) WriteDiskConfig(c *ServerConfig) { s.OnWriteDiskConfig(c) } 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)...) return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...)
} }
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases // FindMACbyIP returns a MAC address by the IP address of its lease, if there is
func (s *server) FindMACbyIP(ip net.IP) net.HardwareAddr { // one.
if ip.To4() != nil { func (s *server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
if ip.Is4() {
return s.srv4.FindMACbyIP(ip) return s.srv4.FindMACbyIP(ip)
} }
return s.srv6.FindMACbyIP(ip) return s.srv6.FindMACbyIP(ip)
} }

View File

@ -4,23 +4,26 @@ package dhcpd
// 'u-root/u-root' package, a dependency of 'insomniacslk/dhcp' package, doesn't build on Windows // '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 winServer struct{}
// type check // type check
var _ DHCPServer = winServer{} var _ DHCPServer = winServer{}
func (winServer) ResetLeases(_ []*Lease) (err error) { return nil } func (winServer) ResetLeases(_ []*Lease) (err error) { return nil }
func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil } func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil }
func (winServer) getLeasesRef() []*Lease { return nil } func (winServer) getLeasesRef() []*Lease { return nil }
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil } func (winServer) AddStaticLease(_ *Lease) (err error) { return nil }
func (winServer) RemoveStaticLease(_ *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) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
func (winServer) WriteDiskConfig4(_ *V4ServerConf) {} func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {} func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
func (winServer) Start() (err error) { return nil } func (winServer) Start() (err error) { return nil }
func (winServer) Stop() (err error) { return nil } func (winServer) Stop() (err error) { return nil }
func v4Create(_ *V4ServerConf) (s DHCPServer, err error) { return winServer{}, nil } func v4Create(_ *V4ServerConf) (s DHCPServer, err error) { return winServer{}, nil }
func v6Create(_ V6ServerConf) (s DHCPServer, err error) { return winServer{}, nil } func v6Create(_ V6ServerConf) (s DHCPServer, err error) { return winServer{}, nil }

View File

@ -200,20 +200,20 @@ func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
return leases return leases
} }
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases // FindMACbyIP implements the [Interface] for *v4Server.
func (s *v4Server) FindMACbyIP(ip net.IP) net.HardwareAddr { func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
now := time.Now() now := time.Now()
s.leasesLock.Lock() s.leasesLock.Lock()
defer s.leasesLock.Unlock() defer s.leasesLock.Unlock()
ip4 := ip.To4() if !ip.Is4() {
if ip4 == nil {
return nil return nil
} }
netIP := ip.AsSlice()
for _, l := range s.leases { for _, l := range s.leases {
if l.IP.Equal(ip4) { if l.IP.Equal(netIP) {
if l.Expiry.After(now) || l.IsStatic() { if l.Expiry.After(now) || l.IsStatic() {
return l.HWAddr return l.HWAddr
} }

View File

@ -6,6 +6,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"net" "net"
"net/netip"
"sync" "sync"
"time" "time"
@ -107,21 +108,26 @@ func (s *v6Server) getLeasesRef() []*Lease {
return s.leases return s.leases
} }
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases // FindMACbyIP implements the [Interface] for *v6Server.
func (s *v6Server) FindMACbyIP(ip net.IP) net.HardwareAddr { func (s *v6Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
now := time.Now().Unix() now := time.Now()
s.leasesLock.Lock() s.leasesLock.Lock()
defer s.leasesLock.Unlock() defer s.leasesLock.Unlock()
if !ip.Is6() {
return nil
}
netIP := ip.AsSlice()
for _, l := range s.leases { for _, l := range s.leases {
if l.IP.Equal(ip) { if l.IP.Equal(netIP) {
unix := l.Expiry.Unix() if l.Expiry.After(now) || l.IsStatic() {
if unix > now || unix == leaseExpireStatic {
return l.HWAddr return l.HWAddr
} }
} }
} }
return nil return nil
} }

View File

@ -53,7 +53,6 @@ const (
// The zero FilteringConfig is empty and ready for use. // The zero FilteringConfig is empty and ready for use.
type FilteringConfig struct { type FilteringConfig struct {
// Callbacks for other modules // Callbacks for other modules
// --
// FilterHandler is an optional additional filtering callback. // FilterHandler is an optional additional filtering callback.
FilterHandler func(clientAddr net.IP, clientID string, settings *filtering.Settings) `yaml:"-"` 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:"-"` GetCustomUpstreamByClient func(id string) (conf *proxy.UpstreamConfig, err error) `yaml:"-"`
// Protection configuration // Protection configuration
// --
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of filtering features // ProtectionEnabled defines whether or not use any of filtering features.
BlockingMode BlockingMode `yaml:"blocking_mode"` // mode how to answer filtered requests ProtectionEnabled bool `yaml:"protection_enabled"`
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)
// IP (or domain name) which is used to respond to DNS requests blocked by parental control or safe-browsing // BlockingMode defines the way how blocked responses are constructed.
ParentalBlockHost string `yaml:"parental_block_host"` 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"` SafeBrowsingBlockHost string `yaml:"safebrowsing_block_host"`
// Anti-DNS amplification // Anti-DNS amplification
// --
Ratelimit uint32 `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable) // Ratelimit is the maximum number of requests per second from a given IP
RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` // a list of whitelisted client IP addresses // (0 to disable).
RefuseAny bool `yaml:"refuse_any"` // if true, refuse ANY requests 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 // Upstream DNS servers configuration
// --
UpstreamDNS []string `yaml:"upstream_dns"` // UpstreamDNS is the list of upstream DNS servers.
UpstreamDNSFileName string `yaml:"upstream_dns_file"` UpstreamDNS []string `yaml:"upstream_dns"`
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 // UpstreamDNSFileName, if set, points to the file which contains upstream
FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm // 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 // FastestTimeout replaces the default timeout for dialing IP addresses
// when FastestAddr is true. // when FastestAddr is true.
FastestTimeout timeutil.Duration `yaml:"fastest_timeout"` FastestTimeout timeutil.Duration `yaml:"fastest_timeout"`
// Access settings // Access settings
// --
// AllowedClients is the slice of IP addresses, CIDR networks, and ClientIDs // AllowedClients is the slice of IP addresses, CIDR networks, and
// of allowed clients. If not empty, only these clients are allowed, and // ClientIDs of allowed clients. If not empty, only these clients are
// [FilteringConfig.DisallowedClients] are ignored. // allowed, and [FilteringConfig.DisallowedClients] are ignored.
AllowedClients []string `yaml:"allowed_clients"` AllowedClients []string `yaml:"allowed_clients"`
// DisallowedClients is the slice of IP addresses, CIDR networks, and // DisallowedClients is the slice of IP addresses, CIDR networks, and
// ClientIDs of disallowed clients. // ClientIDs of disallowed clients.
DisallowedClients []string `yaml:"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 // TrustedProxies is the list of IP addresses and CIDR networks to detect
// proxy servers addresses the DoH requests from which should be handled. // 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 // 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"` TrustedProxies []string `yaml:"trusted_proxies"`
// DNS cache settings // DNS cache settings
// --
CacheSize uint32 `yaml:"cache_size"` // DNS cache size (in bytes) // CacheSize is the DNS cache size (in bytes).
CacheMinTTL uint32 `yaml:"cache_ttl_min"` // override TTL value (minimum) received from upstream server CacheSize uint32 `yaml:"cache_size"`
CacheMaxTTL uint32 `yaml:"cache_ttl_max"` // override TTL value (maximum) received from upstream server
// 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 defines if optimistic cache mechanism should be used.
CacheOptimistic bool `yaml:"cache_optimistic"` CacheOptimistic bool `yaml:"cache_optimistic"`
// Other settings // Other settings
// --
BogusNXDomain []string `yaml:"bogus_nxdomain"` // transform responses with these IP addresses to NXDOMAIN // BogusNXDomain is the list of IP addresses, responses with them will be
AAAADisabled bool `yaml:"aaaa_disabled"` // Respond with an empty answer to all AAAA requests // transformed to NXDOMAIN.
EnableDNSSEC bool `yaml:"enable_dnssec"` // Set AD flag in outcoming DNS request BogusNXDomain []string `yaml:"bogus_nxdomain"`
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
// IpsetList is the ipset configuration that allows AdGuard Home to add // AAAADisabled, if true, respond with an empty answer to all AAAA
// IP addresses of the specified domain names to an ipset list. Syntax: // 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"`
// 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 // DOMAIN[,DOMAIN].../IPSET_NAME
// //
@ -146,6 +197,18 @@ type FilteringConfig struct {
IpsetListFileName string `yaml:"ipset_file"` 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 // TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
type TLSConfig struct { type TLSConfig struct {
cert tls.Certificate cert tls.Certificate
@ -270,12 +333,24 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
UpstreamConfig: srvConf.UpstreamConfig, UpstreamConfig: srvConf.UpstreamConfig,
BeforeRequestHandler: s.beforeRequestHandler, BeforeRequestHandler: s.beforeRequestHandler,
RequestHandler: s.handleDNSRequest, RequestHandler: s.handleDNSRequest,
EnableEDNSClientSubnet: srvConf.EnableEDNSClientSubnet, EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
MaxGoroutines: int(srvConf.MaxGoroutines), MaxGoroutines: int(srvConf.MaxGoroutines),
UseDNS64: srvConf.UseDNS64, UseDNS64: srvConf.UseDNS64,
DNS64Prefs: srvConf.DNS64Prefixes, 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 { if srvConf.CacheSize != 0 {
conf.CacheEnabled = true conf.CacheEnabled = true
conf.CacheSizeBytes = int(srvConf.CacheSize) conf.CacheSizeBytes = int(srvConf.CacheSize)

View File

@ -287,6 +287,9 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
UseDNS64: true, UseDNS64: true,
FilteringConfig: FilteringConfig{
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
}, localUps) }, localUps)
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {

View File

@ -467,6 +467,11 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
s := createTestServer(t, &filtering.Config{}, ServerConfig{ s := createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, 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) }, ups)
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ups} s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ups}
startDeferStop(t, s) startDeferStop(t, s)
@ -539,6 +544,9 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
ServerConfig{ ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
FilteringConfig: FilteringConfig{
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
}, },
aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) { aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
return aghalg.Coalesce( return aghalg.Coalesce(

View File

@ -11,6 +11,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"net" "net"
"net/netip"
"sync" "sync"
"sync/atomic" "sync/atomic"
"testing" "testing"
@ -155,6 +156,9 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte)
s = createTestServer(t, &filtering.Config{}, ServerConfig{ s = createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
FilteringConfig: FilteringConfig{
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
}, nil) }, nil)
tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem
@ -266,6 +270,9 @@ func TestServer(t *testing.T) {
s := createTestServer(t, &filtering.Config{}, ServerConfig{ s := createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
FilteringConfig: FilteringConfig{
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
}, nil) }, nil)
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()} s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
startDeferStop(t, s) startDeferStop(t, s)
@ -304,7 +311,8 @@ func TestServer_timeout(t *testing.T) {
srvConf := &ServerConfig{ srvConf := &ServerConfig{
UpstreamTimeout: timeout, UpstreamTimeout: timeout,
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
BlockingMode: BlockingModeDefault, BlockingMode: BlockingModeDefault,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
}, },
} }
@ -322,6 +330,9 @@ func TestServer_timeout(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{
Enabled: false,
}
err = s.Prepare(&s.conf) err = s.Prepare(&s.conf)
require.NoError(t, err) require.NoError(t, err)
@ -333,6 +344,9 @@ func TestServerWithProtectionDisabled(t *testing.T) {
s := createTestServer(t, &filtering.Config{}, ServerConfig{ s := createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
FilteringConfig: FilteringConfig{
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
}, nil) }, nil)
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()} s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
startDeferStop(t, s) startDeferStop(t, s)
@ -437,6 +451,9 @@ func TestSafeSearch(t *testing.T) {
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
ProtectionEnabled: true, ProtectionEnabled: true,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
} }
s := createTestServer(t, filterConf, forwardConf, nil) s := createTestServer(t, filterConf, forwardConf, nil)
@ -492,6 +509,11 @@ func TestInvalidRequest(t *testing.T) {
s := createTestServer(t, &filtering.Config{}, ServerConfig{ s := createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
FilteringConfig: FilteringConfig{
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
},
}, nil) }, nil)
startDeferStop(t, s) startDeferStop(t, s)
@ -518,6 +540,9 @@ func TestBlockedRequest(t *testing.T) {
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
ProtectionEnabled: true, ProtectionEnabled: true,
BlockingMode: BlockingModeDefault, BlockingMode: BlockingModeDefault,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
} }
s := createTestServer(t, &filtering.Config{}, forwardConf, nil) s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
@ -543,6 +568,9 @@ func TestServerCustomClientUpstream(t *testing.T) {
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
ProtectionEnabled: true, ProtectionEnabled: true,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
} }
s := createTestServer(t, &filtering.Config{}, forwardConf, nil) s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
@ -591,6 +619,11 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
s := createTestServer(t, &filtering.Config{}, ServerConfig{ s := createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
FilteringConfig: FilteringConfig{
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
},
}, nil) }, nil)
testUpstm := &aghtest.Upstream{ testUpstm := &aghtest.Upstream{
CName: testCNAMEs, CName: testCNAMEs,
@ -621,6 +654,9 @@ func TestBlockCNAME(t *testing.T) {
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
ProtectionEnabled: true, ProtectionEnabled: true,
BlockingMode: BlockingModeDefault, BlockingMode: BlockingModeDefault,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
} }
s := createTestServer(t, &filtering.Config{}, forwardConf, nil) s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
@ -690,6 +726,9 @@ func TestClientRulesForCNAMEMatching(t *testing.T) {
FilterHandler: func(_ net.IP, _ string, settings *filtering.Settings) { FilterHandler: func(_ net.IP, _ string, settings *filtering.Settings) {
settings.FilteringEnabled = false settings.FilteringEnabled = false
}, },
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
} }
s := createTestServer(t, &filtering.Config{}, forwardConf, nil) s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
@ -731,6 +770,9 @@ func TestNullBlockedRequest(t *testing.T) {
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
ProtectionEnabled: true, ProtectionEnabled: true,
BlockingMode: BlockingModeNullIP, BlockingMode: BlockingModeNullIP,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
} }
s := createTestServer(t, &filtering.Config{}, forwardConf, nil) s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
@ -783,6 +825,9 @@ func TestBlockedCustomIP(t *testing.T) {
BlockingMode: BlockingModeCustomIP, BlockingMode: BlockingModeCustomIP,
BlockingIPv4: nil, BlockingIPv4: nil,
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, 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{ FilteringConfig: FilteringConfig{
ProtectionEnabled: true, ProtectionEnabled: true,
BlockingMode: BlockingModeDefault, BlockingMode: BlockingModeDefault,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
} }
@ -864,6 +912,9 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
SafeBrowsingBlockHost: ans4.String(), SafeBrowsingBlockHost: ans4.String(),
ProtectionEnabled: true, ProtectionEnabled: true,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
} }
s := createTestServer(t, filterConf, forwardConf, nil) s := createTestServer(t, filterConf, forwardConf, nil)
@ -918,6 +969,9 @@ func TestRewrite(t *testing.T) {
ProtectionEnabled: true, ProtectionEnabled: true,
BlockingMode: BlockingModeDefault, BlockingMode: BlockingModeDefault,
UpstreamDNS: []string{"8.8.8.8:53"}, UpstreamDNS: []string{"8.8.8.8:53"},
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
})) }))
@ -1009,7 +1063,7 @@ var testDHCP = &dhcpd.MockInterface{
}} }}
}, },
OnSetOnLeaseChanged: func(olct dhcpd.OnLeaseChangedT) {}, 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") }, 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.UpstreamDNS = []string{"127.0.0.1:53"}
s.conf.FilteringConfig.ProtectionEnabled = true s.conf.FilteringConfig.ProtectionEnabled = true
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
err = s.Prepare(&s.conf) err = s.Prepare(&s.conf)
require.NoError(t, err) require.NoError(t, err)
@ -1107,6 +1162,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
s.conf.TCPListenAddrs = []*net.TCPAddr{{}} s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
err = s.Prepare(&s.conf) err = s.Prepare(&s.conf)
require.NoError(t, err) require.NoError(t, err)

View File

@ -29,6 +29,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
ProtectionEnabled: true, ProtectionEnabled: true,
BlockingMode: BlockingModeDefault, BlockingMode: BlockingModeDefault,
EDNSClientSubnet: &EDNSClientSubnet{
Enabled: false,
},
}, },
} }
filters := []filtering.Filter{{ filters := []filtering.Filter{{

View File

@ -57,7 +57,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
blockingIPv4 := s.conf.BlockingIPv4 blockingIPv4 := s.conf.BlockingIPv4
blockingIPv6 := s.conf.BlockingIPv6 blockingIPv6 := s.conf.BlockingIPv6
ratelimit := s.conf.Ratelimit ratelimit := s.conf.Ratelimit
enableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet enableEDNSClientSubnet := s.conf.EDNSClientSubnet.Enabled
enableDNSSEC := s.conf.EnableDNSSEC enableDNSSEC := s.conf.EnableDNSSEC
aaaaDisabled := s.conf.AAAADisabled aaaaDisabled := s.conf.AAAADisabled
cacheSize := s.conf.CacheSize 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.LocalPTRResolvers, dc.LocalPTRUpstreams),
setIfNotNil(&s.conf.UpstreamDNSFileName, dc.UpstreamsFile), setIfNotNil(&s.conf.UpstreamDNSFileName, dc.UpstreamsFile),
setIfNotNil(&s.conf.BootstrapDNS, dc.Bootstraps), 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.CacheSize, dc.CacheSize),
setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL), setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL),
setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL), setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL),

View File

@ -69,6 +69,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
ProtectionEnabled: true, ProtectionEnabled: true,
BlockingMode: BlockingModeDefault, BlockingMode: BlockingModeDefault,
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
}, },
ConfigModified: func() {}, ConfigModified: func() {},
} }
@ -144,6 +145,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
ProtectionEnabled: true, ProtectionEnabled: true,
BlockingMode: BlockingModeDefault, BlockingMode: BlockingModeDefault,
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
}, },
ConfigModified: func() {}, ConfigModified: func() {},
} }
@ -227,7 +229,10 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
require.True(t, ok) require.True(t, ok)
t.Run(tc.name, func(t *testing.T) { 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)) rBody := io.NopCloser(bytes.NewReader(caseData.Req))
var r *http.Request var r *http.Request
@ -443,6 +448,9 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
UpstreamTimeout: upsTimeout, UpstreamTimeout: upsTimeout,
FilteringConfig: FilteringConfig{
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
}, nil) }, nil)
startDeferStop(t, srv) startDeferStop(t, srv)

View File

@ -71,6 +71,7 @@ var blockedServices = []blockedService{{
"||amazon.jp^", "||amazon.jp^",
"||amazon.nl^", "||amazon.nl^",
"||amazon.red^", "||amazon.red^",
"||amazon.se^",
"||amazon.sg^", "||amazon.sg^",
"||amazon^", "||amazon^",
"||amazonalexavoxcon.com^", "||amazonalexavoxcon.com^",
@ -1290,6 +1291,14 @@ var blockedServices = []blockedService{{
"||iq.com^", "||iq.com^",
"||iqiyi.com^", "||iqiyi.com^",
}, },
}, {
ID: "kakaotalk",
Name: "KakaoTalk",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\" ><path d=\"M22.125 0H1.875C.839 0 0 .84 0 1.875v20.25C0 23.161.84 24 1.875 24h20.25C23.161 24 24 23.16 24 22.125V1.875C24 .839 23.16 0 22.125 0zM12 18.75c-.591 0-1.17-.041-1.732-.12-.562.396-3.813 2.679-4.12 2.722 0 0-.125.049-.232-.014s-.088-.229-.088-.229c.032-.22.843-3.018.992-3.533-2.745-1.36-4.57-3.769-4.57-6.513 0-4.246 4.365-7.688 9.75-7.688s9.75 3.442 9.75 7.688c0 4.245-4.365 7.687-9.75 7.687zM8.05 9.867h-.878v3.342c0 .296-.252.537-.563.537s-.562-.24-.562-.537V9.867h-.878a.552.552 0 0 1 0-1.101h2.88a.552.552 0 0 1 0 1.101zm10.987 2.957a.558.558 0 0 1 .109.417.559.559 0 0 1-.219.37.557.557 0 0 1-.338.114.558.558 0 0 1-.45-.224l-1.319-1.747-.195.195v1.227a.564.564 0 0 1-.562.563.563.563 0 0 1-.563-.563V9.328a.563.563 0 0 1 1.125 0v1.21l1.57-1.57a.437.437 0 0 1 .311-.126c.14 0 .282.061.388.167a.555.555 0 0 1 .165.356.438.438 0 0 1-.124.343l-1.282 1.281 1.385 1.835zm-8.35-3.502c-.095-.27-.383-.548-.75-.556-.366.008-.654.286-.749.555l-1.345 3.541c-.171.53-.022.728.133.8a.857.857 0 0 0 .357.077c.235 0 .414-.095.468-.248l.279-.73h1.715l.279.73c.054.153.233.248.468.248a.86.86 0 0 0 .357-.078c.155-.071.304-.268.133-.8l-1.345-3.54zm-1.311 2.443.562-1.596.561 1.596H9.376zm5.905 1.383a.528.528 0 0 1-.539.516h-1.804a.528.528 0 0 1-.54-.516v-3.82c0-.31.258-.562.575-.562s.574.252.574.562v3.305h1.195c.297 0 .54.231.54.515z\"/></svg>"),
Rules: []string{
"||kakao.com^",
"||kgslb.com^",
},
}, { }, {
ID: "leagueoflegends", ID: "leagueoflegends",
Name: "League of Legends", Name: "League of Legends",
@ -1336,13 +1345,13 @@ var blockedServices = []blockedService{{
"||aus.social^", "||aus.social^",
"||awscommunity.social^", "||awscommunity.social^",
"||cyberplace.social^", "||cyberplace.social^",
"||defcon.social^",
"||det.social^", "||det.social^",
"||fosstodon.org^", "||fosstodon.org^",
"||glasgow.social^", "||glasgow.social^",
"||h4.io^", "||h4.io^",
"||hachyderm.io^", "||hachyderm.io^",
"||hessen.social^", "||hessen.social^",
"||home.social^",
"||hostux.social^", "||hostux.social^",
"||ieji.de^", "||ieji.de^",
"||indieweb.social^", "||indieweb.social^",

102
internal/home/client.go Normal file
View File

@ -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"`
}

View File

@ -2,7 +2,6 @@ package home
import ( import (
"bytes" "bytes"
"encoding"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
@ -25,122 +24,16 @@ import (
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
const clientsUpdatePeriod = 10 * time.Minute // clientsContainer is the storage of all runtime and persistent clients.
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"`
}
type clientsContainer struct { type clientsContainer struct {
// TODO(a.garipov): Perhaps use a number of separate indices for // TODO(a.garipov): Perhaps use a number of separate indices for different
// different types (string, netip.Addr, and so on). // types (string, netip.Addr, and so on).
list map[string]*Client // name -> client list map[string]*Client // name -> client
idIndex map[string]*Client // ID -> client idIndex map[string]*Client // ID -> client
// ipToRC is the IP address to *RuntimeClient map. // ipToRC is the IP address to *RuntimeClient map.
ipToRC map[netip.Addr]*RuntimeClient ipToRC map[netip.Addr]*RuntimeClient
lock sync.Mutex
allTags *stringutil.Set allTags *stringutil.Set
// dhcpServer is used for looking up clients IP addresses by MAC addresses // 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 stores the neighbors retrieved from ARP.
arpdb aghnet.ARPDB 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 // 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() { func (clients *clientsContainer) Start() {
if !clients.testing { if clients.testing {
if !webHandlersRegistered { return
webHandlersRegistered = true
clients.registerWebHandlers()
}
go clients.periodicUpdate()
} }
if !webHandlersRegistered {
webHandlersRegistered = true
clients.registerWebHandlers()
}
go clients.periodicUpdate()
} }
// Reload reloads runtime clients. // reloadARP reloads runtime clients from ARP, if configured.
func (clients *clientsContainer) Reload() { func (clients *clientsContainer) reloadARP() {
if clients.arpdb != nil { if clients.arpdb != nil {
clients.addFromSystemARP() clients.addFromSystemARP()
} }
} }
// clientObject is the YAML representation of a persistent client.
type clientObject struct { type clientObject struct {
Name string `yaml:"name"` Name string `yaml:"name"`
@ -317,12 +229,15 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
return objs return objs
} }
// arpClientsUpdatePeriod defines how often ARP clients are updated.
const arpClientsUpdatePeriod = 10 * time.Minute
func (clients *clientsContainer) periodicUpdate() { func (clients *clientsContainer) periodicUpdate() {
defer log.OnPanic("clients container") defer log.OnPanic("clients container")
for { for {
clients.Reload() clients.reloadARP()
time.Sleep(clientsUpdatePeriod) time.Sleep(arpClientsUpdatePeriod)
} }
} }
@ -485,7 +400,8 @@ func (clients *clientsContainer) findUpstreams(
return conf, nil 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) { func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
c, ok = clients.idIndex[id] c, ok = clients.idIndex[id]
if ok { if ok {
@ -499,13 +415,13 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
for _, c = range clients.list { for _, c = range clients.list {
for _, id := range c.IDs { for _, id := range c.IDs {
var n netip.Prefix var subnet netip.Prefix
n, err = netip.ParsePrefix(id) subnet, err = netip.ParsePrefix(id)
if err != nil { if err != nil {
continue continue
} }
if n.Contains(ip) { if subnet.Contains(ip) {
return c, true return c, true
} }
} }
@ -515,20 +431,25 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
return nil, false return nil, false
} }
macFound := clients.dhcpServer.FindMACbyIP(ip.AsSlice()) return clients.findDHCP(ip)
if macFound == nil { }
// 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 return nil, false
} }
for _, c = range clients.list { for _, c = range clients.list {
for _, id := range c.IDs { for _, id := range c.IDs {
var mac net.HardwareAddr mac, err := net.ParseMAC(id)
mac, err = net.ParseMAC(id)
if err != nil { if err != nil {
continue continue
} }
if bytes.Equal(mac, macFound) { if bytes.Equal(mac, foundMAC) {
return c, true return c, true
} }
} }
@ -565,24 +486,13 @@ func (clients *clientsContainer) check(c *Client) (err error) {
} }
for i, id := range c.IDs { for i, id := range c.IDs {
// Normalize structured data. var norm string
var ( norm, err = normalizeClientIdentifier(id)
ip netip.Addr if err != nil {
n netip.Prefix return fmt.Errorf("client at index %d: %w", i, err)
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)
} }
c.IDs[i] = norm
} }
for _, t := range c.Tags { for _, t := range c.Tags {
@ -601,6 +511,35 @@ func (clients *clientsContainer) check(c *Client) (err error) {
return nil 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. // if an error occurred.
func (clients *clientsContainer) Add(c *Client) (ok bool, err error) { func (clients *clientsContainer) Add(c *Client) (ok bool, err error) {
@ -666,21 +605,6 @@ func (clients *clientsContainer) Del(name string) (ok bool) {
return true 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. // Update updates a client by its name.
func (clients *clientsContainer) Update(name string, c *Client) (err error) { func (clients *clientsContainer) Update(name string, c *Client) (err error) {
err = clients.check(c) err = clients.check(c)
@ -704,22 +628,11 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
} }
} }
// Second, check the IP index. // Second, update the ID index.
if !equalStringSlices(prev.IDs, c.IDs) { err = clients.updateIDIndex(prev, c.IDs)
for _, id := range c.IDs { if err != nil {
c2, ok2 := clients.idIndex[id] // Don't wrap the error, because it's informative enough as is.
if ok2 && c2 != prev { return err
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
}
} }
// Update name index. // Update name index.
@ -739,6 +652,32 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
return nil 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. // setWHOISInfo sets the WHOIS information for a client.
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) { func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) {
clients.lock.Lock() clients.lock.Lock()

View File

@ -75,11 +75,21 @@ type osConfig struct {
type clientsConfig struct { type clientsConfig struct {
// Sources defines the set of sources to fetch the runtime clients from. // 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 are the configured clients.
Persistent []*clientObject `yaml:"persistent"` 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 // configuration is loaded from YAML
// field ordering is important -- yaml fields will mirror ordering from here // field ordering is important -- yaml fields will mirror ordering from here
type configuration struct { type configuration struct {
@ -334,7 +344,7 @@ var config = &configuration{
}, },
}, },
Clients: &clientsConfig{ Clients: &clientsConfig{
Sources: &clientSourcesConf{ Sources: &clientSourcesConfig{
WHOIS: true, WHOIS: true,
ARP: true, ARP: true,
RDNS: true, RDNS: true,

View File

@ -125,7 +125,7 @@ func Main(clientBuildFS fs.FS) {
log.Info("Received signal %q", sig) log.Info("Received signal %q", sig)
switch sig { switch sig {
case syscall.SIGHUP: case syscall.SIGHUP:
Context.clients.Reload() Context.clients.reloadARP()
Context.tls.reload() Context.tls.reload()
default: default:
cleanup(context.Background()) cleanup(context.Background())

View File

@ -90,6 +90,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
upgradeSchema14to15, upgradeSchema14to15,
upgradeSchema15to16, upgradeSchema15to16,
upgradeSchema16to17, upgradeSchema16to17,
upgradeSchema17to18,
} }
n := 0 n := 0
@ -793,7 +794,7 @@ func upgradeSchema13to14(diskConf yobj) (err error) {
diskConf["clients"] = yobj{ diskConf["clients"] = yobj{
"persistent": clientsVal, "persistent": clientsVal,
"runtime_sources": &clientSourcesConf{ "runtime_sources": &clientSourcesConfig{
WHOIS: true, WHOIS: true,
ARP: true, ARP: true,
RDNS: rdnsSrc, RDNS: rdnsSrc,
@ -893,13 +894,13 @@ func upgradeSchema15to16(diskConf yobj) (err error) {
"ignored": []any{}, "ignored": []any{},
} }
k := "statistics_interval" const field = "statistics_interval"
v, has := dns[k] v, has := dns[field]
if has { if has {
stats["enabled"] = v != 0 stats["enabled"] = v != 0
stats["interval"] = v stats["interval"] = v
} }
delete(dns, k) delete(dns, field)
diskConf["statistics"] = stats diskConf["statistics"] = stats
@ -909,15 +910,52 @@ func upgradeSchema15to16(diskConf yobj) (err error) {
// upgradeSchema16to17 performs the following changes: // upgradeSchema16to17 performs the following changes:
// //
// # BEFORE: // # 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': // 'statistics':
// 'interval': 1 // 'interval': 1
// //
// # AFTER: // # AFTER:
// 'statistics': // 'statistics':
// 'interval': 24h // 'interval': 24h
func upgradeSchema16to17(diskConf yobj) (err error) { func upgradeSchema17to18(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 16 to 17") log.Printf("Upgrade yaml: 17 to 18")
diskConf["schema_version"] = 17 diskConf["schema_version"] = 18
statsVal, ok := diskConf["statistics"] statsVal, ok := diskConf["statistics"]
if !ok { if !ok {

View File

@ -579,7 +579,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
// The clients field will be added anyway. // The clients field will be added anyway.
"clients": yobj{ "clients": yobj{
"persistent": yarr{}, "persistent": yarr{},
"runtime_sources": &clientSourcesConf{ "runtime_sources": &clientSourcesConfig{
WHOIS: true, WHOIS: true,
ARP: true, ARP: true,
RDNS: false, RDNS: false,
@ -597,7 +597,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
"schema_version": newSchemaVer, "schema_version": newSchemaVer,
"clients": yobj{ "clients": yobj{
"persistent": []*clientObject{testClient}, "persistent": []*clientObject{testClient},
"runtime_sources": &clientSourcesConf{ "runtime_sources": &clientSourcesConfig{
WHOIS: true, WHOIS: true,
ARP: true, ARP: true,
RDNS: false, RDNS: false,
@ -618,7 +618,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
"schema_version": newSchemaVer, "schema_version": newSchemaVer,
"clients": yobj{ "clients": yobj{
"persistent": []*clientObject{testClient}, "persistent": []*clientObject{testClient},
"runtime_sources": &clientSourcesConf{ "runtime_sources": &clientSourcesConfig{
WHOIS: true, WHOIS: true,
ARP: true, ARP: true,
RDNS: true, RDNS: true,
@ -749,6 +749,67 @@ func TestUpgradeSchema15to16(t *testing.T) {
} }
func TestUpgradeSchema16to17(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 { testCases := []struct {
ivl any ivl any
want any want any
@ -771,10 +832,10 @@ func TestUpgradeSchema16to17(t *testing.T) {
"statistics": yobj{ "statistics": yobj{
"interval": tc.ivl, "interval": tc.ivl,
}, },
"schema_version": 16, "schema_version": 17,
} }
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
err := upgradeSchema16to17(conf) err := upgradeSchema17to18(conf)
if tc.wantErr != "" { if tc.wantErr != "" {
require.Error(t, err) require.Error(t, err)
@ -785,7 +846,7 @@ func TestUpgradeSchema16to17(t *testing.T) {
} }
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, conf["schema_version"], 17) require.Equal(t, conf["schema_version"], 18)
statsVal, ok := conf["statistics"] statsVal, ok := conf["statistics"]
require.True(t, ok) require.True(t, ok)
@ -803,13 +864,13 @@ func TestUpgradeSchema16to17(t *testing.T) {
} }
t.Run("no_stats", func(t *testing.T) { t.Run("no_stats", func(t *testing.T) {
err := upgradeSchema16to17(yobj{}) err := upgradeSchema17to18(yobj{})
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("bad_stats", func(t *testing.T) { t.Run("bad_stats", func(t *testing.T) {
err := upgradeSchema16to17(yobj{ err := upgradeSchema17to18(yobj{
"statistics": 0, "statistics": 0,
}) })
@ -821,7 +882,7 @@ func TestUpgradeSchema16to17(t *testing.T) {
"statistics": yobj{}, "statistics": yobj{},
} }
err := upgradeSchema16to17(conf) err := upgradeSchema17to18(conf)
require.NoError(t, err) require.NoError(t, err)
statsVal, ok := conf["statistics"] statsVal, ok := conf["statistics"]

View File

@ -315,7 +315,7 @@ func (u *Updater) clean() {
// MaxPackageFileSize is a maximum package file length in bytes. The largest // MaxPackageFileSize is a maximum package file length in bytes. The largest
// package whose size is limited by this constant currently has the size of // package whose size is limited by this constant currently has the size of
// approximately 9 MiB. // approximately 9 MiB.
const MaxPackageFileSize = 32 * 10 * 1024 const MaxPackageFileSize = 32 * 1024 * 1024
// Download package file and save it to disk // Download package file and save it to disk
func (u *Updater) downloadPackageFile() (err error) { func (u *Updater) downloadPackageFile() (err error) {