From 7b93f5d7cfe6c1932aa3ba35e2adee8698db09e2 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 7 Sep 2023 17:13:48 +0300 Subject: [PATCH] all: sync with master; upd chlog --- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 2 +- AGHTechDoc.md | 4 +- CHANGELOG.md | 187 +- bamboo-specs/release.yaml | 6 +- bamboo-specs/snapcraft.yaml | 6 +- bamboo-specs/test.yaml | 55 +- client/.eslintrc.json | 15 +- client/dev.eslintrc | 2 +- client/package-lock.json | 5 + client/package.json | 1 + client/src/__locales/ar.json | 17 +- client/src/__locales/be.json | 52 +- client/src/__locales/bg.json | 2 +- client/src/__locales/cs.json | 47 +- client/src/__locales/da.json | 47 +- client/src/__locales/de.json | 47 +- client/src/__locales/en.json | 51 +- client/src/__locales/es.json | 47 +- client/src/__locales/fa.json | 59 +- client/src/__locales/fi.json | 47 +- client/src/__locales/fr.json | 63 +- client/src/__locales/hr.json | 45 +- client/src/__locales/hu.json | 49 +- client/src/__locales/id.json | 31 +- client/src/__locales/it.json | 47 +- client/src/__locales/ja.json | 63 +- client/src/__locales/ko.json | 47 +- client/src/__locales/nl.json | 47 +- client/src/__locales/no.json | 57 +- client/src/__locales/pl.json | 47 +- client/src/__locales/pt-br.json | 51 +- client/src/__locales/pt-pt.json | 47 +- client/src/__locales/ro.json | 47 +- client/src/__locales/ru.json | 45 +- client/src/__locales/si-lk.json | 169 +- client/src/__locales/sk.json | 47 +- client/src/__locales/sl.json | 47 +- client/src/__locales/sr-cs.json | 47 +- client/src/__locales/sv.json | 59 +- client/src/__locales/th.json | 9 +- client/src/__locales/tr.json | 47 +- client/src/__locales/uk.json | 45 +- client/src/__locales/vi.json | 47 +- client/src/__locales/zh-cn.json | 47 +- client/src/__locales/zh-hk.json | 9 +- client/src/__locales/zh-tw.json | 47 +- client/src/actions/dnsConfig.js | 4 + client/src/actions/index.js | 10 +- client/src/actions/services.js | 16 +- client/src/actions/stats.js | 2 + client/src/api/Api.js | 10 +- client/src/components/App/index.css | 16 +- client/src/components/App/index.js | 2 +- .../components/Dashboard/UpstreamAvgTime.js | 79 + .../components/Dashboard/UpstreamResponses.js | 81 + client/src/components/Dashboard/index.js | 96 +- client/src/components/Filters/Form.js | 1 - .../Filters/Services/ScheduleForm/Modal.js | 220 + .../Services/ScheduleForm/TimePeriod.js | 25 + .../Services/ScheduleForm/TimeSelect.js | 60 + .../Filters/Services/ScheduleForm/Timezone.js | 46 + .../Filters/Services/ScheduleForm/helpers.js | 46 + .../Filters/Services/ScheduleForm/index.js | 140 + .../Filters/Services/ScheduleForm/styles.css | 134 + .../src/components/Filters/Services/index.js | 33 +- client/src/components/Logs/Logs.css | 6 + .../Clients/ClientsTable/ClientsTable.js | 8 +- .../src/components/Settings/Clients/Form.js | 34 +- .../components/Settings/Dns/Upstream/Form.js | 25 + .../components/Settings/Dns/Upstream/index.js | 4 + .../components/Settings/LogsConfig/Form.js | 6 +- .../components/Settings/StatsConfig/Form.js | 2 + client/src/components/ui/Cell.js | 44 +- client/src/components/ui/Icons.js | 14 + client/src/components/ui/Line.js | 2 +- client/src/components/ui/Modal.css | 3 +- client/src/components/ui/PageTitle.css | 4 +- client/src/components/ui/Tabler.css | 6 +- client/src/containers/Settings.js | 4 +- client/src/helpers/constants.js | 2 + client/src/helpers/filters/filters.js | 36 + client/src/helpers/trackers/trackers.json | 92 +- client/src/install/Setup/Setup.css | 3 +- client/src/reducers/dnsConfig.js | 2 + client/src/reducers/services.js | 8 +- client/src/reducers/stats.js | 4 + go.mod | 34 +- go.sum | 106 +- internal/aghhttp/aghhttp.go | 21 - internal/aghhttp/header.go | 5 +- internal/aghhttp/json.go | 142 + internal/aghhttp/json_test.go | 114 + internal/aghnet/dhcp_unix.go | 2 +- internal/aghnet/hostscontainer.go | 422 +- .../aghnet/hostscontainer_internal_test.go | 68 - internal/aghnet/hostscontainer_test.go | 500 +- internal/aghnet/ignore.go | 56 + internal/aghnet/ignore_test.go | 46 + .../{interfaces_unix.go => interfaces_bsd.go} | 16 +- internal/aghnet/ipset_linux.go | 6 +- internal/aghnet/net_internal_test.go | 4 - internal/aghos/fswatcher.go | 2 +- internal/aghos/os.go | 5 + internal/aghos/os_unix.go | 4 + internal/aghos/os_windows.go | 4 + internal/aghtest/aghtest.go | 14 +- internal/{aghnet => arpdb}/arpdb.go | 94 +- internal/{aghnet => arpdb}/arpdb_bsd.go | 31 +- .../arpdb_bsd_internal_test.go} | 2 +- .../arpdb_internal_test.go} | 72 +- internal/{aghnet => arpdb}/arpdb_linux.go | 92 +- .../arpdb_linux_internal_test.go} | 2 +- internal/{aghnet => arpdb}/arpdb_openbsd.go | 2 +- .../arpdb_openbsd_internal_test.go} | 2 +- internal/{aghnet => arpdb}/arpdb_windows.go | 19 +- .../arpdb_windows_internal_test.go} | 2 +- .../{aghnet => arpdb}/testdata/proc_net_arp | 0 internal/client/client.go | 49 + .../migrations_test.go} | 459 +- internal/confmigrate/migrator.go | 140 + internal/confmigrate/migrator_test.go | 208 + .../TestMigrateConfig_Migrate/v1/input.yml | 31 + .../TestMigrateConfig_Migrate/v1/output.yml | 32 + .../TestMigrateConfig_Migrate/v10/input.yml | 60 + .../TestMigrateConfig_Migrate/v10/output.yml | 60 + .../TestMigrateConfig_Migrate/v11/input.yml | 61 + .../TestMigrateConfig_Migrate/v11/output.yml | 64 + .../TestMigrateConfig_Migrate/v12/input.yml | 65 + .../TestMigrateConfig_Migrate/v12/output.yml | 65 + .../TestMigrateConfig_Migrate/v13/input.yml | 65 + .../TestMigrateConfig_Migrate/v13/output.yml | 65 + .../TestMigrateConfig_Migrate/v14/input.yml | 66 + .../TestMigrateConfig_Migrate/v14/output.yml | 72 + .../TestMigrateConfig_Migrate/v15/input.yml | 74 + .../TestMigrateConfig_Migrate/v15/output.yml | 76 + .../TestMigrateConfig_Migrate/v16/input.yml | 77 + .../TestMigrateConfig_Migrate/v16/output.yml | 80 + .../TestMigrateConfig_Migrate/v17/input.yml | 81 + .../TestMigrateConfig_Migrate/v17/output.yml | 84 + .../TestMigrateConfig_Migrate/v18/input.yml | 84 + .../TestMigrateConfig_Migrate/v18/output.yml | 91 + .../TestMigrateConfig_Migrate/v19/input.yml | 91 + .../TestMigrateConfig_Migrate/v19/output.yml | 98 + .../TestMigrateConfig_Migrate/v2/input.yml | 34 + .../TestMigrateConfig_Migrate/v2/output.yml | 34 + .../TestMigrateConfig_Migrate/v20/input.yml | 98 + .../TestMigrateConfig_Migrate/v20/output.yml | 98 + .../TestMigrateConfig_Migrate/v21/input.yml | 100 + .../TestMigrateConfig_Migrate/v21/output.yml | 103 + .../TestMigrateConfig_Migrate/v22/input.yml | 105 + .../TestMigrateConfig_Migrate/v22/output.yml | 108 + .../TestMigrateConfig_Migrate/v23/input.yml | 109 + .../TestMigrateConfig_Migrate/v23/output.yml | 109 + .../TestMigrateConfig_Migrate/v24/input.yml | 116 + .../TestMigrateConfig_Migrate/v24/output.yml | 117 + .../TestMigrateConfig_Migrate/v25/input.yml | 118 + .../TestMigrateConfig_Migrate/v25/output.yml | 120 + .../TestMigrateConfig_Migrate/v26/input.yml | 120 + .../TestMigrateConfig_Migrate/v26/output.yml | 121 + .../TestMigrateConfig_Migrate/v27/input.yml | 123 + .../TestMigrateConfig_Migrate/v27/output.yml | 123 + .../TestMigrateConfig_Migrate/v3/input.yml | 33 + .../TestMigrateConfig_Migrate/v3/output.yml | 34 + .../TestMigrateConfig_Migrate/v4/input.yml | 43 + .../TestMigrateConfig_Migrate/v4/output.yml | 44 + .../TestMigrateConfig_Migrate/v5/input.yml | 44 + .../TestMigrateConfig_Migrate/v5/output.yml | 45 + .../TestMigrateConfig_Migrate/v6/input.yml | 45 + .../TestMigrateConfig_Migrate/v6/output.yml | 48 + .../TestMigrateConfig_Migrate/v7/input.yml | 53 + .../TestMigrateConfig_Migrate/v7/output.yml | 54 + .../TestMigrateConfig_Migrate/v8/input.yml | 57 + .../TestMigrateConfig_Migrate/v8/output.yml | 58 + .../TestMigrateConfig_Migrate/v9/input.yml | 59 + .../TestMigrateConfig_Migrate/v9/output.yml | 59 + internal/confmigrate/v1.go | 35 + internal/confmigrate/v10.go | 118 + internal/confmigrate/v11.go | 33 + internal/confmigrate/v12.go | 43 + internal/confmigrate/v13.go | 32 + internal/confmigrate/v14.go | 62 + internal/confmigrate/v15.go | 52 + internal/confmigrate/v16.go | 78 + internal/confmigrate/v17.go | 39 + internal/confmigrate/v18.go | 45 + internal/confmigrate/v19.go | 72 + internal/confmigrate/v2.go | 37 + internal/confmigrate/v20.go | 44 + internal/confmigrate/v21.go | 49 + internal/confmigrate/v22.go | 72 + internal/confmigrate/v23.go | 59 + internal/confmigrate/v24.go | 50 + internal/confmigrate/v25.go | 38 + internal/confmigrate/v26.go | 111 + internal/confmigrate/v27.go | 77 + internal/confmigrate/v3.go | 31 + internal/confmigrate/v4.go | 30 + internal/confmigrate/v5.go | 47 + internal/confmigrate/v6.go | 56 + internal/confmigrate/v7.go | 55 + internal/confmigrate/v8.go | 36 + internal/confmigrate/v9.go | 27 + internal/confmigrate/yaml.go | 68 + internal/dhcpd/README.md | 10 +- internal/dhcpd/config.go | 15 +- internal/dhcpd/conn_bsd.go | 25 +- internal/dhcpd/conn_linux.go | 25 +- internal/dhcpd/conn_unix.go | 24 + internal/dhcpd/db.go | 5 +- internal/dhcpd/dhcpd.go | 127 +- internal/dhcpd/http_unix.go | 35 +- internal/dhcpd/http_windows.go | 2 +- internal/dhcpd/v46_windows.go | 2 + internal/dhcpd/v4_unix.go | 74 +- internal/dhcpd/v4_unix_test.go | 10 +- internal/dhcpd/v6_unix.go | 43 +- internal/dhcpsvc/dhcpsvc.go | 5 +- internal/dnsforward/access.go | 4 +- internal/dnsforward/config.go | 102 +- internal/dnsforward/dialcontext.go | 5 +- internal/dnsforward/dns64_test.go | 6 +- internal/dnsforward/dnsforward.go | 151 +- internal/dnsforward/dnsforward_test.go | 262 +- internal/dnsforward/dnsrewrite.go | 16 +- internal/dnsforward/dnsrewrite_test.go | 17 +- internal/dnsforward/filter.go | 87 +- internal/dnsforward/filter_test.go | 244 +- internal/dnsforward/http.go | 86 +- internal/dnsforward/http_test.go | 58 +- internal/dnsforward/msg.go | 140 +- internal/dnsforward/process.go | 166 +- internal/dnsforward/process_internal_test.go | 266 +- internal/dnsforward/stats.go | 8 +- internal/dnsforward/stats_test.go | 4 +- internal/dnsforward/svcbmsg_test.go | 13 +- .../TestDNSForwardHTTP_handleGetConfig.json | 9 + .../TestDNSForwardHTTP_handleSetConfig.json | 61 + internal/dnsforward/upstreams.go | 71 +- internal/filtering/blocked.go | 94 +- internal/filtering/dnsrewrite.go | 58 + internal/filtering/dnsrewrite_test.go | 178 +- internal/filtering/filter.go | 90 +- internal/filtering/filter_test.go | 129 +- internal/filtering/filtering.go | 395 +- internal/filtering/filtering_test.go | 11 +- .../hashprefix/hashprefix_internal_test.go | 4 +- internal/filtering/http.go | 87 +- .../filtering/rewrite/item_internal_test.go | 2 +- internal/filtering/rewrite/storage_test.go | 85 +- internal/filtering/rewritehttp.go | 80 +- internal/filtering/rewrites.go | 99 +- internal/filtering/rewrites_test.go | 153 +- internal/filtering/safesearch/safesearch.go | 32 +- .../safesearch/safesearch_internal_test.go | 9 +- .../filtering/safesearch/safesearch_test.go | 9 +- internal/filtering/safesearchhttp.go | 24 +- internal/filtering/servicelist.go | 180 +- internal/home/client.go | 48 +- internal/home/clients.go | 162 +- internal/home/clients_internal_test.go | 64 +- internal/home/clientshttp.go | 86 +- internal/home/config.go | 202 +- internal/home/control.go | 58 +- internal/home/controlinstall.go | 4 +- internal/home/controlupdate.go | 4 +- internal/home/dns.go | 35 +- internal/home/dns_internal_test.go | 8 +- internal/home/home.go | 132 +- internal/home/home_test.go | 2 +- internal/home/i18n.go | 2 +- internal/home/log.go | 106 + internal/home/profilehttp.go | 2 +- internal/home/service.go | 23 +- internal/home/tls.go | 2 +- internal/home/upgrade.go | 1440 ----- internal/home/web.go | 8 +- internal/querylog/decode.go | 14 +- internal/querylog/decode_test.go | 16 +- internal/querylog/http.go | 13 +- internal/querylog/qlog.go | 1 - internal/querylog/qlog_test.go | 31 +- internal/querylog/qlogreader.go | 10 +- internal/querylog/querylog.go | 6 +- internal/querylog/search.go | 4 +- internal/schedule/schedule.go | 152 +- internal/schedule/schedule_internal_test.go | 146 +- internal/stats/http.go | 21 +- internal/stats/http_test.go | 23 - internal/stats/stats.go | 52 +- internal/stats/stats_internal_test.go | 105 +- internal/stats/stats_test.go | 60 +- internal/stats/unit.go | 362 +- internal/stats/unit_internal_test.go | 177 + internal/tools/go.mod | 23 +- internal/tools/go.sum | 51 +- openapi/CHANGELOG.md | 124 +- openapi/openapi.yaml | 147 +- openapi/v1.yaml | 5041 +++++++++++++++++ scripts/blocked-services/main.go | 5 +- scripts/install.sh | 2 +- scripts/make/go-lint.sh | 121 +- scripts/make/go-tools.sh | 2 +- scripts/make/helper.sh | 4 +- scripts/make/txt-lint.sh | 32 +- scripts/snap/upload.sh | 4 +- 306 files changed, 19770 insertions(+), 4916 deletions(-) create mode 100644 client/src/components/Dashboard/UpstreamAvgTime.js create mode 100644 client/src/components/Dashboard/UpstreamResponses.js create mode 100644 client/src/components/Filters/Services/ScheduleForm/Modal.js create mode 100644 client/src/components/Filters/Services/ScheduleForm/TimePeriod.js create mode 100644 client/src/components/Filters/Services/ScheduleForm/TimeSelect.js create mode 100644 client/src/components/Filters/Services/ScheduleForm/Timezone.js create mode 100644 client/src/components/Filters/Services/ScheduleForm/helpers.js create mode 100644 client/src/components/Filters/Services/ScheduleForm/index.js create mode 100644 client/src/components/Filters/Services/ScheduleForm/styles.css create mode 100644 internal/aghhttp/json.go create mode 100644 internal/aghhttp/json_test.go create mode 100644 internal/aghnet/ignore.go create mode 100644 internal/aghnet/ignore_test.go rename internal/aghnet/{interfaces_unix.go => interfaces_bsd.go} (76%) rename internal/{aghnet => arpdb}/arpdb.go (61%) rename internal/{aghnet => arpdb}/arpdb_bsd.go (74%) rename internal/{aghnet/arpdb_bsd_test.go => arpdb/arpdb_bsd_internal_test.go} (98%) rename internal/{aghnet/arpdb_test.go => arpdb/arpdb_internal_test.go} (69%) rename internal/{aghnet => arpdb}/arpdb_linux.go (78%) rename internal/{aghnet/arpdb_linux_test.go => arpdb/arpdb_linux_internal_test.go} (99%) rename internal/{aghnet => arpdb}/arpdb_openbsd.go (98%) rename internal/{aghnet/arpdb_openbsd_test.go => arpdb/arpdb_openbsd_internal_test.go} (97%) rename internal/{aghnet => arpdb}/arpdb_windows.go (81%) rename internal/{aghnet/arpdb_windows_test.go => arpdb/arpdb_windows_internal_test.go} (97%) rename internal/{aghnet => arpdb}/testdata/proc_net_arp (100%) rename internal/{home/upgrade_test.go => confmigrate/migrations_test.go} (74%) create mode 100644 internal/confmigrate/migrator.go create mode 100644 internal/confmigrate/migrator_test.go create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/output.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/input.yml create mode 100644 internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/output.yml create mode 100644 internal/confmigrate/v1.go create mode 100644 internal/confmigrate/v10.go create mode 100644 internal/confmigrate/v11.go create mode 100644 internal/confmigrate/v12.go create mode 100644 internal/confmigrate/v13.go create mode 100644 internal/confmigrate/v14.go create mode 100644 internal/confmigrate/v15.go create mode 100644 internal/confmigrate/v16.go create mode 100644 internal/confmigrate/v17.go create mode 100644 internal/confmigrate/v18.go create mode 100644 internal/confmigrate/v19.go create mode 100644 internal/confmigrate/v2.go create mode 100644 internal/confmigrate/v20.go create mode 100644 internal/confmigrate/v21.go create mode 100644 internal/confmigrate/v22.go create mode 100644 internal/confmigrate/v23.go create mode 100644 internal/confmigrate/v24.go create mode 100644 internal/confmigrate/v25.go create mode 100644 internal/confmigrate/v26.go create mode 100644 internal/confmigrate/v27.go create mode 100644 internal/confmigrate/v3.go create mode 100644 internal/confmigrate/v4.go create mode 100644 internal/confmigrate/v5.go create mode 100644 internal/confmigrate/v6.go create mode 100644 internal/confmigrate/v7.go create mode 100644 internal/confmigrate/v8.go create mode 100644 internal/confmigrate/v9.go create mode 100644 internal/confmigrate/yaml.go create mode 100644 internal/dhcpd/conn_unix.go create mode 100644 internal/home/log.go delete mode 100644 internal/home/upgrade.go create mode 100644 internal/stats/unit_internal_test.go create mode 100644 openapi/v1.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da8ca358..c9a48519 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ 'name': 'build' 'env': - 'GO_VERSION': '1.20.7' + 'GO_VERSION': '1.20.8' 'NODE_VERSION': '14' 'on': diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 773f0494..a3c68e6a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,7 @@ 'name': 'lint' 'env': - 'GO_VERSION': '1.20.7' + 'GO_VERSION': '1.20.8' 'on': 'push': diff --git a/AGHTechDoc.md b/AGHTechDoc.md index c1b53300..42c15a46 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -835,7 +835,7 @@ Request: Response: 200 OK - + ### API: Validate TLS configuration Request: @@ -2008,7 +2008,7 @@ Request: Response: 200 OK - + DOH plist file ## API: Get DNS over TLS .mobileconfig diff --git a/CHANGELOG.md b/CHANGELOG.md index f56595b0..d8403683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,11 @@ and this project adheres to @@ -29,6 +29,174 @@ NOTE: Add new changes ABOVE THIS COMMENT. +## [v0.107.37] - 2023-09-07 + +See also the [v0.107.37 GitHub milestone][ms-v0.107.37]. + +### Security + +- Go version has been updated to prevent the possibility of exploiting the + CVE-2023-39318, CVE-2023-39319, and CVE-2023-39320 Go vulnerabilities fixed in + [Go 1.20.8][go-1.20.8]. + +### Added + +- AdBlock-style syntax support for ignored domains in logs and statistics + ([#5720]). +- [`Strict-Transport-Security`][hsts] header in the HTTP API and DNS-over-HTTPS + responses when HTTPS is forced ([#2998]). See [RFC 6797][rfc6797]. +- UI for the schedule of the service-blocking pause ([#951]). +- IPv6 hints are now filtered in case IPv6 addresses resolving is disabled + ([#6122]). +- The ability to set fallback DNS servers in the configuration file and the UI + ([#3701]). +- While adding or updating blocklists, the title can now be parsed from + `! Title:` definition of the blocklist's source ([#6020]). +- The ability to filter DNS HTTPS records including IPv4 and IPv6 hints + ([#6053]). +- Two new metrics showing total number of responses from each upstream DNS + server and their average processing time in the Web UI ([#1453]). +- The ability to set the port for the `pprof` debug API, see configuration + changes below. + +### Changed + +- `$dnsrewrite` rules containing IPv4-mapped IPv6 addresses are now working + consistently with legacy DNS rewrites and match the `AAAA` requests. +- For non-A and non-AAAA requests, which has been filtered, the NODATA response + is returned if the blocking mode isn't set to `Null IP`. In previous versions + it returned NXDOMAIN response in such cases. + +#### Configuration Changes + +In this release, the schema version has changed from 24 to 27. + +- Ignore rules blocking `.` in `querylog.ignored` and `statistics.ignored` have + been migrated to AdBlock syntax (`|.^`). To rollback this change, restore the + rules and change the `schema_version` back to `26`. + +- Filtering-related settings have been moved from `dns` section of the YAML + configuration file to the new section `filtering`: + + ```yaml + # BEFORE: + 'dns': + 'filtering_enabled': true + 'filters_update_interval': 24 + 'parental_enabled': false + 'safebrowsing_enabled': false + 'safebrowsing_cache_size': 1048576 + 'safesearch_cache_size': 1048576 + 'parental_cache_size': 1048576 + 'safe_search': + 'enabled': false + 'bing': true + 'duckduckgo': true + 'google': true + 'pixabay': true + 'yandex': true + 'youtube': true + 'rewrites': [] + 'blocked_services': + 'schedule': + 'time_zone': 'Local' + 'ids': [] + 'protection_enabled': true, + 'blocking_mode': 'custom_ip', + 'blocking_ipv4': '1.2.3.4', + 'blocking_ipv6': '1:2:3::4', + 'blocked_response_ttl': 10, + 'protection_disabled_until': 'null', + 'parental_block_host': 'p.dns.adguard.com', + 'safebrowsing_block_host': 's.dns.adguard.com' + + # AFTER: + 'filtering': + 'filtering_enabled': true + 'filters_update_interval': 24 + 'parental_enabled': false + 'safebrowsing_enabled': false + 'safebrowsing_cache_size': 1048576 + 'safesearch_cache_size': 1048576 + 'parental_cache_size': 1048576 + 'safe_search': + 'enabled': false + 'bing': true + 'duckduckgo': true + 'google': true + 'pixabay': true + 'yandex': true + 'youtube': true + 'rewrites': [] + 'blocked_services': + 'schedule': + 'time_zone': 'Local' + 'ids': [] + 'protection_enabled': true, + 'blocking_mode': 'custom_ip', + 'blocking_ipv4': '1.2.3.4', + 'blocking_ipv6': '1:2:3::4', + 'blocked_response_ttl': 10, + 'protection_disabled_until': 'null', + 'parental_block_host': 'p.dns.adguard.com', + 'safebrowsing_block_host': 's.dns.adguard.com', + ``` + + To rollback this change, remove the new object `filtering`, set back filtering + properties in `dns` section, and change the `schema_version` back to `25`. + +- Property `debug_pprof` which used to setup profiling HTTP handler, is now + moved to the new `pprof` object under `http` section. The new object contains + properties `enabled` and `port`: + + ```yaml + # BEFORE: + 'debug_pprof': true + + # AFTER: + 'http': + 'pprof': + 'enabled': true + 'port': 6060 + ``` + + Note that the new default `6060` is used as default. To rollback this change, + remove the new object `pprof`, set back `debug_pprof`, and change the + `schema_version` back to `24`. + +### Fixed + +- Incorrect display date on statistics graph ([#5793]). +- Missing query log entries and statistics on service restart ([#6100]). +- Occasional DNS-over-QUIC and DNS-over-HTTP/3 errors ([#6133]). +- Legacy DNS rewrites containing IPv4-mapped IPv6 addresses are now matching the + `AAAA` requests, not `A` ([#6050]). +- File log configuration, such as `max_size`, being ignored ([#6093]). +- Panic on using a single-slash filtering rule. +- Panic on shutting down while DNS requests are in process of filtering + ([#5948]). + +[#1453]: https://github.com/AdguardTeam/AdGuardHome/issues/1453 +[#2998]: https://github.com/AdguardTeam/AdGuardHome/issues/2998 +[#3701]: https://github.com/AdguardTeam/AdGuardHome/issues/3701 +[#5720]: https://github.com/AdguardTeam/AdGuardHome/issues/5720 +[#5793]: https://github.com/AdguardTeam/AdGuardHome/issues/5793 +[#5948]: https://github.com/AdguardTeam/AdGuardHome/issues/5948 +[#6020]: https://github.com/AdguardTeam/AdGuardHome/issues/6020 +[#6050]: https://github.com/AdguardTeam/AdGuardHome/issues/6050 +[#6053]: https://github.com/AdguardTeam/AdGuardHome/issues/6053 +[#6093]: https://github.com/AdguardTeam/AdGuardHome/issues/6093 +[#6100]: https://github.com/AdguardTeam/AdGuardHome/issues/6100 +[#6122]: https://github.com/AdguardTeam/AdGuardHome/issues/6122 +[#6133]: https://github.com/AdguardTeam/AdGuardHome/issues/6133 + +[go-1.20.8]: https://groups.google.com/g/golang-announce/c/Fm51GRLNRvM/m/F5bwBlXMAQAJ +[hsts]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security +[ms-v0.107.37]: https://github.com/AdguardTeam/AdGuardHome/milestone/72?closed=1 +[rfc6797]: https://datatracker.ietf.org/doc/html/rfc6797 + + + ## [v0.107.36] - 2023-08-02 See also the [v0.107.36 GitHub milestone][ms-v0.107.36]. @@ -539,7 +707,7 @@ In this release, the schema version has changed from 17 to 20. [#5701]: https://github.com/AdguardTeam/AdGuardHome/issues/5701 [ms-v0.107.28]: https://github.com/AdguardTeam/AdGuardHome/milestone/64?closed=1 -[rfc6761]: https://www.rfc-editor.org/rfc/rfc6761 +[rfc6761]: https://datatracker.ietf.org/doc/html/rfc6761 @@ -627,7 +795,6 @@ See also the [v0.107.26 GitHub milestone][ms-v0.107.26]. 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 [#5281]: https://github.com/AdguardTeam/AdGuardHome/issues/5281 @@ -1246,7 +1413,6 @@ See also the [v0.107.10 GitHub milestone][ms-v0.107.10]. [#4342]: https://github.com/AdguardTeam/AdGuardHome/issues/4342 [#4358]: https://github.com/AdguardTeam/AdGuardHome/issues/4358 [#4670]: https://github.com/AdguardTeam/AdGuardHome/issues/4670 -[#4836]: https://github.com/AdguardTeam/AdGuardHome/issues/4836 [#4843]: https://github.com/AdguardTeam/AdGuardHome/issues/4843 [ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08 @@ -1880,9 +2046,7 @@ In this release, the schema version has changed from 10 to 12. [#3558]: https://github.com/AdguardTeam/AdGuardHome/issues/3558 [#3564]: https://github.com/AdguardTeam/AdGuardHome/issues/3564 [#3567]: https://github.com/AdguardTeam/AdGuardHome/issues/3567 -[#3568]: https://github.com/AdguardTeam/AdGuardHome/issues/3568 [#3579]: https://github.com/AdguardTeam/AdGuardHome/issues/3579 -[#3607]: https://github.com/AdguardTeam/AdGuardHome/issues/3607 [#3638]: https://github.com/AdguardTeam/AdGuardHome/issues/3638 [#3655]: https://github.com/AdguardTeam/AdGuardHome/issues/3655 [#3707]: https://github.com/AdguardTeam/AdGuardHome/issues/3707 @@ -2300,11 +2464,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2]. -[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.36...HEAD +[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.37...HEAD +[v0.107.37]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.36...v0.107.37 [v0.107.36]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.35...v0.107.36 [v0.107.35]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.34...v0.107.35 [v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...v0.107.34 diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml index d4efb97b..7ee86906 100644 --- a/bamboo-specs/release.yaml +++ b/bamboo-specs/release.yaml @@ -7,7 +7,7 @@ # Make sure to sync any changes with the branch overrides below. 'variables': 'channel': 'edge' - 'dockerGo': 'adguard/golang-ubuntu:7.0' + 'dockerGo': 'adguard/golang-ubuntu:7.1' 'stages': - 'Build frontend': @@ -272,7 +272,7 @@ # need to build a few of these. 'variables': 'channel': 'beta' - 'dockerGo': 'adguard/golang-ubuntu:7.0' + 'dockerGo': 'adguard/golang-ubuntu:7.1' # release-vX.Y.Z branches are the branches from which the actual final # release is built. - '^release-v[0-9]+\.[0-9]+\.[0-9]+': @@ -287,4 +287,4 @@ # are the ones that actually get released. 'variables': 'channel': 'release' - 'dockerGo': 'adguard/golang-ubuntu:7.0' + 'dockerGo': 'adguard/golang-ubuntu:7.1' diff --git a/bamboo-specs/snapcraft.yaml b/bamboo-specs/snapcraft.yaml index 7d8c5f38..20df25bd 100644 --- a/bamboo-specs/snapcraft.yaml +++ b/bamboo-specs/snapcraft.yaml @@ -10,7 +10,7 @@ # Make sure to sync any changes with the branch overrides below. 'variables': 'channel': 'edge' - 'dockerGo': 'adguard/golang-ubuntu:7.0' + 'dockerGo': 'adguard/golang-ubuntu:7.1' 'snapcraftChannel': 'edge' 'stages': @@ -191,7 +191,7 @@ # need to build a few of these. 'variables': 'channel': 'beta' - 'dockerGo': 'adguard/golang-ubuntu:7.0' + 'dockerGo': 'adguard/golang-ubuntu:7.1' 'snapcraftChannel': 'beta' # release-vX.Y.Z branches are the branches from which the actual final # release is built. @@ -207,5 +207,5 @@ # are the ones that actually get released. 'variables': 'channel': 'release' - 'dockerGo': 'adguard/golang-ubuntu:7.0' + 'dockerGo': 'adguard/golang-ubuntu:7.1' 'snapcraftChannel': 'candidate' diff --git a/bamboo-specs/test.yaml b/bamboo-specs/test.yaml index fe858950..33cad20c 100644 --- a/bamboo-specs/test.yaml +++ b/bamboo-specs/test.yaml @@ -5,7 +5,7 @@ 'key': 'AHBRTSPECS' 'name': 'AdGuard Home - Build and run tests' 'variables': - 'dockerGo': 'adguard/golang-ubuntu:7.0' + 'dockerGo': 'adguard/golang-ubuntu:7.1' 'stages': - 'Tests': @@ -14,6 +14,12 @@ 'jobs': - 'Test' + - 'Artifact': + manual: false + final: false + jobs: + - 'Artifact' + 'Test': 'docker': 'image': '${bamboo.dockerGo}' @@ -41,6 +47,53 @@ 'requirements': - 'adg-docker': 'true' +'Artifact': + 'docker': + 'image': '${bamboo.dockerGo}' + 'volumes': + '${system.GO_CACHE_DIR}': '${bamboo.cacheGo}' + '${system.GO_PKG_CACHE_DIR}': '${bamboo.cacheGoPkg}' + 'key': 'ART' + 'other': + 'clean-working-dir': true + 'tasks': + - 'checkout': + 'force-clean-build': true + - 'script': + 'interpreter': 'SHELL' + 'scripts': + - |- + #!/bin/sh + + set -e -f -u -x + + # Explicitly checkout the revision that we need. + git checkout "${bamboo.repository.revision.number}" + + make\ + ARCH="amd64"\ + OS="windows darwin linux"\ + CHANNEL="development"\ + SIGN=0\ + PARALLELISM=1\ + VERBOSE=2\ + build-release + 'artifacts': + - 'name': 'AdGuardHome_windows_amd64' + 'pattern': 'dist/AdGuardHome_windows_amd64.zip' + 'shared': true + 'required': true + - 'name': 'AdGuardHome_darwin_amd64' + 'pattern': 'dist/AdGuardHome_darwin_amd64.zip' + 'shared': true + 'required': true + - 'name': 'AdGuardHome_linux_amd64' + 'pattern': 'dist/AdGuardHome_linux_amd64.tar.gz' + 'shared': true + 'required': true + 'requirements': + - 'adg-docker': 'true' + 'branches': 'create': 'for-pull-request' 'delete': diff --git a/client/.eslintrc.json b/client/.eslintrc.json index 0098e76a..e7dd1e89 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -81,6 +81,19 @@ } ], "import/prefer-default-export": "off", - "no-alert": "off" + "no-alert": "off", + "arrow-body-style": "off", + "max-len": [ + "error", + 120, + 2, + { + "ignoreUrls": true, + "ignoreComments": false, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + } + ] } } diff --git a/client/dev.eslintrc b/client/dev.eslintrc index 27341caf..9d7e5493 100644 --- a/client/dev.eslintrc +++ b/client/dev.eslintrc @@ -1,6 +1,6 @@ { "extends": ".eslintrc", "rules": { - "no-debugger":"warn", + "no-debugger":"warn" } } diff --git a/client/package-lock.json b/client/package-lock.json index ba1d3772..cc6c55ea 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -15094,6 +15094,11 @@ "setimmediate": "^1.0.4" } }, + "timezones-list": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/timezones-list/-/timezones-list-3.0.2.tgz", + "integrity": "sha512-I698hm6Jp/xxkwyTSOr39pZkYKETL8LDJeSIhjxXBfPUAHM5oZNuQ4o9UK3PSkDBOkjATecSOBb3pR1IkIBUsg==" + }, "tiny-invariant": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", diff --git a/client/package.json b/client/package.json index 22b83010..134b98b5 100644 --- a/client/package.json +++ b/client/package.json @@ -43,6 +43,7 @@ "redux-form": "^8.3.5", "redux-thunk": "^2.3.0", "string-length": "^5.0.1", + "timezones-list": "^3.0.2", "url-polyfill": "^1.1.9" }, "devDependencies": { diff --git a/client/src/__locales/ar.json b/client/src/__locales/ar.json index 55b176ad..c9f0f415 100644 --- a/client/src/__locales/ar.json +++ b/client/src/__locales/ar.json @@ -7,7 +7,7 @@ "load_balancing": "توزيع الحمل", "load_balancing_desc": "الاستعلام عن خادم واحد في كل مرة سيستخدم AdGuard الرئيسية الخوارزمية العشوائية الموزونة لاختيار الخادم بحيث يتم استخدام أسرع خادم في كثير من الأحيان", "bootstrap_dns": "خوادم Bootstrap DNS", - "bootstrap_dns_desc": "يتم استخدام خوادم Bootstrap DNS لحل عناوين IP الخاصة بمحللات DoH / DoT التي تحددها على هيئة تدفقات.", + "bootstrap_dns_desc": "عناوين IP لخوادم DNS المستخدمة لحل عناوين IP الخاصة بمحللات DoH/DoT التي تحددها كمصدرين رئيسيين. التعليقات غير مسموح بها.", "local_ptr_title": "خوادم DNS العكسية الخاصة", "local_ptr_desc": "خوادم DNS التي يستخدمها AdGuard Home لاستعلامات PTR المحلية. تُستخدم هذه الخوادم لحل أسماء المضيفين للعملاء بعناوين IP خاصة ، على سبيل المثال \"192.168.12.34\" ، باستخدام DNS العكسي. في حالة عدم التعيين ، يستخدم AdGuard Home عناوين محللات DNS الافتراضية لنظام التشغيل الخاص بك باستثناء عناوين AdGuard Home نفسها.", "local_ptr_default_resolver": "بشكل افتراضي ، يستخدم AdGuard Home محللات DNS العكسية التالية: {{ip}}.", @@ -125,6 +125,8 @@ "top_clients": "كبار العملاء", "no_clients_found": "لم يتم العثور على عملاء", "general_statistics": "الإحصاءات العامة", + "top_upstreams": "أعلى الخوادم upstream", + "no_upstreams_data_found": "لم يتم العثور على بيانات خوادم upstream", "number_of_dns_query_days": "عدد استعلامات DNS التي تمت معالجتها لآخر {{count}} يوم", "number_of_dns_query_days_plural": "عدد استعلامات DNS التي تمت معالجتها لآخر {{count}} أيام", "number_of_dns_query_24_hours": "عدد استعلامات DNS التي تمت معالجتها لآخر 24 ساعة", @@ -158,6 +160,7 @@ "upstream_dns_configured_in_file": "تم اعداده في {{path}}", "test_upstream_btn": "اختبار upstream", "upstreams": "Upstreams", + "upstream": "Upstream", "apply_btn": "تطبيق", "disabled_filtering_toast": "تم تعطيل الفلترة", "enabled_filtering_toast": "تم تمكين الفلترة", @@ -212,6 +215,7 @@ "example_upstream_udp": "regular DNS (over UDP, hostname);", "example_upstream_dot": "مشفر<0>DNS-over-TLS;", "example_upstream_doh": "مشفر <0>DNS-over-HTTPS;", + "example_upstream_doh3": "DNS-over-HTTPS المشفر مع فرض <0> HTTP / 3 ولا يوجد رجوع إلى HTTP / 2 أو أقل ؛", "example_upstream_doq": "encrypted <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS Stamps for <1>DNSCrypt or <2>DNS-over-HTTPS resolvers;", "example_upstream_tcp": "regular DNS (over TCP);", @@ -552,7 +556,7 @@ "rewrite_A": "<0> A : قيمة خاصة ، احتفظ بسجلات <0> A من upstream", "rewrite_AAAA": "<0> AAAA : قيمة خاصة ، احتفظ بسجلات <0> AAAA من upstream", "disable_ipv6": "قم بتعطيل تحليل عناوين IPv6", - "disable_ipv6_desc": "قم بإسقاط جميع استعلامات DNS لعناوين IPv6 (اكتب AAAA).", + "disable_ipv6_desc": "قم بإسقاط كافة استعلامات DNS لعناوين IPv6 (اكتب AAAA) وقم بإزالة تلميحات IPv6 من استجابات HTTPS.", "fastest_addr": "أسرع عنوان IP", "fastest_addr_desc": "استعلم عن جميع خوادم DNS وأعد عنوان IP الأسرع بين جميع الاستجابات. يؤدي هذا إلى إبطاء استعلامات DNS حيث يتعين على AdGuard Home انتظار الاستجابات من جميع خوادم DNS ، ولكنه يحسن الاتصال الكلي.", "autofix_warning_text": "إذا قمت بالنقر فوق \"إصلاح\" ، فسيقوم AdGuard Home بتهيئة نظامك لاستخدام خادم AdGuard Home DNS.", @@ -636,5 +640,12 @@ "safe_browsing": "تصفح آمن", "served_from_cache": "{{value}} (يتم تقديمه من ذاكرة التخزين المؤقت)", "form_error_password_length": "يجب أن تتكون كلمة المرور من {{value}} من الأحرف على الأقل", - "protection_section_label": "الحماية" + "protection_section_label": "الحماية", + "sunday_short": "الاحد", + "monday_short": "الإثنين", + "tuesday_short": "الثلاثاء", + "wednesday_short": "الاربعاء", + "thursday_short": "الخميس", + "friday_short": "الجمعة", + "saturday_short": "السبت" } diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json index 5e29a8e4..f93ff15a 100644 --- a/client/src/__locales/be.json +++ b/client/src/__locales/be.json @@ -7,7 +7,10 @@ "load_balancing": "Размеркаванне нагрузкі", "load_balancing_desc": "Запытвайце па адным серверы за раз. AdGuard Home будзе выкарыстоўваць выпадковы алгарытм для выбару сервера, так што самы хуткі сервер будзе выкарыстоўвацца часцей.", "bootstrap_dns": "Bootstrap DNS-серверы", - "bootstrap_dns_desc": "Bootstrap DNS-серверы выкарыстоўваюцца для пошуку IP-адрасоў DoH/DoT сервераў, якія вы паказалі.", + "bootstrap_dns_desc": "IP-адрасы DNS-сервераў, якія выкарыстоўваюцца для вырашэння IP-адрасоў распознавальнікаў DoH/DoT, якія вы ўказваеце ў якасці перадачы. Каментары не дапускаюцца.", + "fallback_dns_title": "Рэзервовыя DNS-серверы", + "fallback_dns_desc": "Спіс рэзервовых DNS-сервераў, якія выкарыстоўваюцца, калі вышэйшыя DNS-серверы не адказваюць. Сінтаксіс такі ж, як і ў галоўным полі ўверх.", + "fallback_dns_placeholder": "Увядзіце па адным рэзервовым серверы DNS у радку", "local_ptr_title": "Прыватныя DNS-серверы", "local_ptr_desc": "DNS-серверы, якія AdGuard Home выкарыстоўвае для лакальных PTR-запытаў. Гэтыя серверы выкарыстоўваюцца, каб атрымаць даменавыя імёны кліентаў з прыватнымі IP-адрасамі, напрыклад «192.168.12.34», з дапамогай rDNS. Калі спіс пусты, AdGuard Home выкарыстоўвае прадвызначаныя DNS-серверы вашай АС.", "local_ptr_default_resolver": "Па змаўчанні AdGuard Home выкарыстоўвае наступныя зваротныя DNS-рэзолверы: {{ip}}.", @@ -125,6 +128,8 @@ "top_clients": "Частыя кліенты", "no_clients_found": "Кліентаў не знойдзена", "general_statistics": "Агульная статыстыка", + "top_upstreams": "Часта запытаныя upstream серверы", + "no_upstreams_data_found": "Няма дадзеных аб upstream серверах", "number_of_dns_query_days": "Колькасць DNS-запытаў за апошні {{count}} дзень", "number_of_dns_query_days_plural": "Колькасць DNS запытаў, апрацаваных за апошнія {{count}} дзён", "number_of_dns_query_24_hours": "Колькасць DNS-запытаў за 24 гадзіны", @@ -134,6 +139,7 @@ "enforced_save_search": "Ужыты бяспечны пошук", "number_of_dns_query_to_safe_search": "Колькасць запытаў DNS для пошукавых сістэм, для якіх быў ужыты Бяспечны пошук", "average_processing_time": "Сярэдні час апрацоўкі запыту", + "processing_time": "Час апрацоўкі", "average_processing_time_hint": "Сярэдні час для апрацоўкі запыту DNS у мілісекундах", "block_domain_use_filters_and_hosts": "Блакаваць дамены з выкарыстаннем фільтраў і файлаў хастоў", "filters_block_toggle_hint": "Вы можаце наладзіць правілы блакавання ў «Фільтрах».", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Наладжаны ў {{path}}", "test_upstream_btn": "Тэст upstream сервераў", "upstreams": "Upstreams", + "upstream": "Upstream сервер", "apply_btn": "Ужыць", "disabled_filtering_toast": "Фільтрацыя выкл.", "enabled_filtering_toast": "Фільтрацыя ўкл.", @@ -441,7 +448,7 @@ "client_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць кліента «{{key}}»?", "list_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць гэты спіс?", "auto_clients_title": "Кліенты (runtime)", - "auto_clients_desc": "Прылады, якіх няма ў спісе пастаянных кліентаў, якія ўсё яшчэ могуць выкарыстоўваць AdGuard Home", + "auto_clients_desc": "Інфармацыя аб IP-адрасах прылад, якія выкарыстоўваюць або могуць выкарыстоўваць AdGuard Home. Гэтая інфармацыя збіраецца з некалькіх крыніц, уключаючы файлы хостаў, зваротны DNS і г.д.", "access_title": "Налады доступу", "access_desc": "Тут вы можаце наладзіць правілы доступу да DNS-серверу AdGuard Home", "access_allowed_title": "Дазволеныя кліенты", @@ -557,7 +564,7 @@ "rewrite_A": "<0>A: адмысловае значэнне, захоўваць запісы <0>A з сервера для выгрузкі даных", "rewrite_AAAA": "<0>AAAA: адмысловае значэнне, захоўваць запісы <0>AAAA з сервера для выгрузкі даных", "disable_ipv6": "Адключыць IPv6", - "disable_ipv6_desc": "Калі гэта опцыя ўлучана, усе DNS-запыты адрасоў IPv6 (тып AAAA) будуць ігнаравацца.", + "disable_ipv6_desc": "Ігнараваць усе запыты DNS для адрасоў IPv6 (тып AAAA) і выдаленне дадзеных IPv6 з адказаў тыпу HTTPS.", "fastest_addr": "Найхуткі IP-адрас", "fastest_addr_desc": "Апытайце ўсе DNS-серверы і вярніце самы хуткі IP-адрас сярод усіх адказаў. Гэта замарудзіць выкананне DNS-запытаў, бо нам давядзецца чакаць адказаў ад усіх DNS-сервераў, але палепшыць агульную ўзаемасувязь.", "autofix_warning_text": "Пры націску «Выправіць» AdGuard Home наладзіць вашу сістэму на выкарыстанне DNS-сервера AdGuard Home.", @@ -645,5 +652,42 @@ "confirm_dns_cache_clear": "Вы ўпэўнены, што хочаце ачысціць кэш DNS?", "cache_cleared": "Кэш DNS паспяхова ачышчаны", "clear_cache": "Ачысціць кэш", - "protection_section_label": "Ахова" + "theme_dark_desc": "Цёмная тэма", + "theme_light_desc": "Светлая тэма", + "protection_section_label": "Ахова", + "log_and_stats_section_label": "Журнал запытаў і статыстыка", + "ignore_query_log": "Ігнараваць гэтага кліента ў журнале запытаў", + "ignore_statistics": "Ігнараваць гэтага кліента ў статыстыцы", + "schedule_services": "Паўза блакавання сэрвісаў", + "schedule_services_desc": "Настройка раскладу паўзы фільтра блакавання сэрвісаў", + "schedule_services_desc_client": "Настройка раскладу паўзы фільтра блакавання сэрвісаў для дадзенага кліента", + "schedule_desc": "Усталюйце перыяды бяздзейнасці для заблакаваных сэрвісаў", + "schedule_invalid_select": "Час пачатку павінен быць перад часам заканчэння", + "schedule_select_days": "Выбрать дни", + "schedule_timezone": "Выберыце гадзінны пояс", + "schedule_current_timezone": "Бягучы гадзінны пояс: {{value}}", + "schedule_time_all_day": "Увесь дзень", + "schedule_modal_description": "Гэты расклад заменіць усе існуючыя расклады на той жа дзень тыдня. Кожны дзень тыдня можа мець толькі адзін перыяд бяздзейнасці.", + "schedule_modal_time_off": "Блакіроўка сэрвісаў адключаная:", + "schedule_new": "Новы расклад", + "schedule_edit": "Рэдагаваць расклад", + "schedule_save": "Захаваць расклад", + "schedule_add": "Дадаць расклад", + "schedule_remove": "Выдаліць расклад", + "schedule_from": "З", + "schedule_to": "Да", + "sunday": "Нядзеля", + "monday": "Панядзелак", + "tuesday": "Аўторак", + "wednesday": "Серада", + "thursday": "Чацвер", + "friday": "Пятніца", + "saturday": "Субота", + "sunday_short": "Нд.", + "monday_short": "Пн.", + "tuesday_short": "Аў.", + "wednesday_short": "Ср.", + "thursday_short": "Чц.", + "friday_short": "Пт.", + "saturday_short": "Сб." } diff --git a/client/src/__locales/bg.json b/client/src/__locales/bg.json index d7808318..50bd1680 100644 --- a/client/src/__locales/bg.json +++ b/client/src/__locales/bg.json @@ -265,7 +265,7 @@ "domain": "Домейн", "ecs": "ECS", "statistics_clear": "Нулирай статистиката", - "disabled": "Деактивиран", + "disabled": "Изключен", "username_label": "Потребител", "username_placeholder": "Въведете потребител", "password_label": "Парола", diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json index 94f89d8a..71fd614a 100644 --- a/client/src/__locales/cs.json +++ b/client/src/__locales/cs.json @@ -7,12 +7,15 @@ "load_balancing": "Optimalizace vytížení", "load_balancing_desc": "Optimalizovaný dotaz na odchozí server. AdGuard Home použije vážený náhodný algoritmus k výběru serveru, takže nejrychlejší server je používán častěji.", "bootstrap_dns": "Bootstrap DNS servery", - "bootstrap_dns_desc": "Servery Bootstrap DNS se používají k řešení IP adres DoH/DoT, které zadáváte jako upstreamy.", + "bootstrap_dns_desc": "IP adresy DNS serverů používaných k překladu IP adres řešitelů DoH/DoT, které zadáte jako odchozí servery. Komentáře nejsou povoleny.", + "fallback_dns_title": "Záložní DNS servery", + "fallback_dns_desc": "Seznam záložních DNS serverů používaných v případě, že odchozí DNS servery neodpovídají. Syntaxe je stejná jako v hlavním poli pro odchozí servery výše.", + "fallback_dns_placeholder": "Zadejte jeden záložní DNS server na řádek", "local_ptr_title": "Soukromé reverzní DNS servery", "local_ptr_desc": "Servery DNS, které AdGuard Home používá pro lokální dotazy PTR. Tyto servery se používají k řešení požadavků PTR na adresy v soukromých rozmezích IP, například \"192.168.12.34\", pomocí reverzního DNS. Pokud není nastaveno, AdGuard Home automaticky použije výchozí řešitele vašeho OS s výjimkou adres samotného AdGuard Home.", "local_ptr_default_resolver": "Ve výchozím nastavení používá AdGuard Home následující reverzní DNS řešitele: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home nemohl určit vhodné soukromé reverzní DNS řešitele pro tento systém.", - "local_ptr_placeholder": "Zadejte jednu adresu serveru na řádek", + "local_ptr_placeholder": "Zadejte jednu IP adresu na řádek", "resolve_clients_title": "Povolit zpětné řešení IP adres klientů", "resolve_clients_desc": "Obráceně vyřešit IP adresy klientů na jejich názvy hostitelů zasláním dotazů PTR příslušným řešitelům (soukromé DNS servery pro místní klienty, odchozí servery pro klienty s veřejnou IP adresou).", "use_private_ptr_resolvers_title": "Použít soukromé reverzní rDNS řešitele", @@ -125,6 +128,8 @@ "top_clients": "Nejčastější klienti", "no_clients_found": "Nenalezeny žádní klienti", "general_statistics": "Obecné statistiky", + "top_upstreams": "Top odchozí připojení", + "no_upstreams_data_found": "Nebyla nalezena žádná data odchozích připojení", "number_of_dns_query_days": "Počet DNS dotazů zpracovaných za posledních {{count}} den", "number_of_dns_query_days_plural": "Počet DNS dotazů zpracovaných za posledních {{count}} dní", "number_of_dns_query_24_hours": "Počet DNS dotazů zpracovaných za posledních 24 hodin", @@ -134,6 +139,7 @@ "enforced_save_search": "Vynucené bezpečné vyhledávání", "number_of_dns_query_to_safe_search": "Počet požadavků DNS na vyhledávače, při kterých bylo vynucené bezpečné vyhledávání", "average_processing_time": "Průměrný čas zpracování", + "processing_time": "Doba zpracování", "average_processing_time_hint": "Průměrný čas zpracování požadavků DNS v milisekundách", "block_domain_use_filters_and_hosts": "Blokovat domény pomocí filtrů a seznamů adres", "filters_block_toggle_hint": "Pravidla blokování můžete nastavit v nastavení Filtry.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Konfigurováno v {{path}}", "test_upstream_btn": "Test upstreamů", "upstreams": "Odesláno", + "upstream": "Odchozí připojení", "apply_btn": "Použít", "disabled_filtering_toast": "Vypnuté filtrování", "enabled_filtering_toast": "Zapnuté filtrování", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: speciální hodnota, udržet záznamy typu <0>A z odchozího serveru", "rewrite_AAAA": "<0>AAAA: speciální hodnota, udržet záznamy typu <0>AAAA z odchozího serveru", "disable_ipv6": "Zakázat řešení IPv6 adres", - "disable_ipv6_desc": "Zrušit všechny dotazy DNS pro adresy IPv6 (typ AAAA).", + "disable_ipv6_desc": "Odstranění všech dotazů DNS na adresy IPv6 (typ AAAA) a odstranění náznaků IPv6 z odpovědí HTTPS.", "fastest_addr": "Nejrychlejší IP adresa", "fastest_addr_desc": "Dotazovat všechny DNS servery a vrátit nejrychlejší IP adresu ze všech odpovědí. To zpomalí dotazy DNS, protože AdGuard Home musí čekat na odpovědi ze všech serverů DNS, ale celková konektivita se zlepší.", "autofix_warning_text": "Pokud kliknete na „Opravit“, AdGuard Home nakonfiguruje váš systém tak, aby používal DNS server AdGuard Home.", @@ -676,5 +683,37 @@ "protection_section_label": "Ochrana", "log_and_stats_section_label": "Protokol dotazů a statistiky", "ignore_query_log": "Ignorovat tohoto klienta v protokolu dotazů", - "ignore_statistics": "Ignorovat tohoto klienta ve statistikách" + "ignore_statistics": "Ignorovat tohoto klienta ve statistikách", + "schedule_services": "Pozastavit blokování služeb", + "schedule_services_desc": "Konfigurace plánu pozastavení filtru blokování služeb", + "schedule_services_desc_client": "Konfigurace plánu pozastavení filtru blokování služeb pro tohoto klienta", + "schedule_desc": "Nastavení doby nečinnosti pro blokované služby", + "schedule_invalid_select": "Čas zahájení musí být před časem ukončení", + "schedule_select_days": "Vyberte dny", + "schedule_timezone": "Vyberte časové pásmo", + "schedule_current_timezone": "Aktuální časové pásmo: {{value}}", + "schedule_time_all_day": "Všechny dny", + "schedule_modal_description": "Tento plán nahradí všechny stávající plány pro stejný den v týdnu. Každý den v týdnu může mít pouze jedno období nečinnosti.", + "schedule_modal_time_off": "Žádné blokování služeb:", + "schedule_new": "Nový plán", + "schedule_edit": "Upravit plán", + "schedule_save": "Uložit plán", + "schedule_add": "Přidat plán", + "schedule_remove": "Odstranit plán", + "schedule_from": "Od", + "schedule_to": "Do", + "sunday": "Neděle", + "monday": "Pondělí", + "tuesday": "Úterý", + "wednesday": "Středa", + "thursday": "Čtvrtek", + "friday": "Pátek", + "saturday": "Sobota", + "sunday_short": "Neděle", + "monday_short": "Pondělí", + "tuesday_short": "Úterý", + "wednesday_short": "Středa", + "thursday_short": "Čtvrtek", + "friday_short": "Pátek", + "saturday_short": "Sobota" } diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json index d8168b8e..85de61d5 100644 --- a/client/src/__locales/da.json +++ b/client/src/__locales/da.json @@ -7,12 +7,15 @@ "load_balancing": "Belastningsfordeling", "load_balancing_desc": "Forespørg én server ad gangen. AdGuard Home vil bruge en vægtet randomiseringsalgoritme til valg af server, så den hurtigste server oftere anvendes.", "bootstrap_dns": "Bootstrap DNS-servere", - "bootstrap_dns_desc": "Bootstrap DNS-servere bruges til at fortolke IP-adresser for de DoH-/DoT-resolvere, du angiver som upstream.", + "bootstrap_dns_desc": "IP-adresser på DNS-servere, som bruges til at opløse IP-adresser på de DoH/DoT-opløsere, som angives som upstreams. Kommentarer er ikke tilladt.", + "fallback_dns_title": "Reserve DNS-servere", + "fallback_dns_desc": "Liste over reserve (fallback) DNS-servere, som bruges, når upstream DNS-servere ikke reagerer. Samme syntaks som i upstream-hovedfeltet ovenfor.", + "fallback_dns_placeholder": "Angiv én reserve DNS-server pr. linje", "local_ptr_title": "Private reverse DNS-servere", "local_ptr_desc": "DNS-servere brugt af AdGuard Home til lokale PTR-forespørgsler. Disse servere bruges til at opløse PTR-forespørgsler fra private IP-adresseområder, f.eks. \"192.168.12.34\", vha. reverse DNS. Hvis ikke opsat, bruger AdGuard Home operativsystems standard DNS-opløsere undtagen for sine egne adresser.", "local_ptr_default_resolver": "AdGuard Home bruger som standard flg. reverse DNS-opløsere: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home kunne ikke fastslå egnede private reverse DNS-opløsere for dette system.", - "local_ptr_placeholder": "Indtast en serveradresse pr. Linje", + "local_ptr_placeholder": "Angiv én IP-adresse pr. linje", "resolve_clients_title": "Aktivér omvendt løsning af klienters IP-adresser", "resolve_clients_desc": "Opløs klienters IP-adresser reverseret til deres værtsnavne ved at sende PTR-forespørgsler til korresponderende opløsere (private DNS-servere til lokale klienter, upstream-servere til klienter med offentlige IP-adresser).", "use_private_ptr_resolvers_title": "Brug private reverse DNS-opløsere", @@ -125,6 +128,8 @@ "top_clients": "Hyppigste klienter", "no_clients_found": "Ingen klienter fundet", "general_statistics": "Generelle statistikker", + "top_upstreams": "Top-upstreams", + "no_upstreams_data_found": "Ingen upstreams-data fundet", "number_of_dns_query_days": "Antallet af DNS-forespørgsler behandlet den seneste {{count}} dag", "number_of_dns_query_days_plural": "Antallet af DNS-forespørgsler behandlet de seneste {{count}} dage", "number_of_dns_query_24_hours": "Antallet af DNS-forespørgsler behandlet de seneste 24 timer", @@ -134,6 +139,7 @@ "enforced_save_search": "Håndhævet sikker søgning", "number_of_dns_query_to_safe_search": "Antallet af DNS-forespørgsler til søgemaskiner, hvor Sikker Søgning blev håndhævet", "average_processing_time": "Gennemsnitlig behandlingstid", + "processing_time": "Behandlingstid", "average_processing_time_hint": "Gennemsnitlig behandlingstid i millisekunder af DNS-forespørgsel", "block_domain_use_filters_and_hosts": "Blokér domæner vha. filtre og værtsfiler", "filters_block_toggle_hint": "Du kan opsætte blokeringsregler i Filterindstillingerne.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Opsat i {{path}}", "test_upstream_btn": "Test upstreams", "upstreams": "Upstreams", + "upstream": "Upstream", "apply_btn": "Anvend", "disabled_filtering_toast": "Filtrering deaktiveret", "enabled_filtering_toast": "Filtrering aktiveret", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: Særlig værdi, hold <0>A poster fra upstream", "rewrite_AAAA": "<0>AAAA: Særlig værdi, hold <0>AAAA poster fra upstream", "disable_ipv6": "Deaktivér IPv6-adresseopløsning", - "disable_ipv6_desc": "Drop alle DNS-forespørgsler for IPv6-adresser (type AAAA).", + "disable_ipv6_desc": "Drop alle DNS-forespørgsler for IPv6-adresser (type AAAA), og fjern IPv6-tips fra HTTPS-svar.", "fastest_addr": "Hurtigste IP-adresse", "fastest_addr_desc": "Forespørger alle DNS-servere og returner den hurtigste IP-adresse blandt alle svar. Dette vil gøre DNS-forespørgslerne langsommere grundet afventning af svar fra alle DNS-servere, men forbedrer samlet set forbindelsen.", "autofix_warning_text": "Klikker du på \"Reparér\", opsætter AdGuard Home dit system til brug med AdGuard Home DNS-server.", @@ -676,5 +683,37 @@ "protection_section_label": "Beskyttelse", "log_and_stats_section_label": "Forespørgselslog og statistik", "ignore_query_log": "Ignorér denne klient i forespørgselslog", - "ignore_statistics": "Ignorér denne klient i statistik" + "ignore_statistics": "Ignorér denne klient i statistik", + "schedule_services": "Pausering af tjenesteblokering", + "schedule_services_desc": "Opsæt pauseringstidsplan for det tjenesteblokerende filter", + "schedule_services_desc_client": "Opsæt pauseringstidsplan for det tjenesteblokerende filter for denne klient", + "schedule_desc": "Sæt inaktivitetsperioder for blokerede tjenester", + "schedule_invalid_select": "Starttidspunkt skal være før sluttidspunkt", + "schedule_select_days": "Vælg dage", + "schedule_timezone": "Vælg tidszone", + "schedule_current_timezone": "Aktuel tidszone: {{value}}", + "schedule_time_all_day": "Hele dagen", + "schedule_modal_description": "Denne tidsplan vil erstatte alle eksisterende tidsplaner for den samme ugedag. Hver ugedag kan kun have én inaktivitetsperiode.", + "schedule_modal_time_off": "Ingen tjenesteblokering:", + "schedule_new": "Ny tidsplan", + "schedule_edit": "Redigér tidsplan", + "schedule_save": "Gem tidsplan", + "schedule_add": "Tilføj tidsplan", + "schedule_remove": "Fjern tidsplan", + "schedule_from": "Fra", + "schedule_to": "Til", + "sunday": "Søndag", + "monday": "Mandag", + "tuesday": "Tirsdag", + "wednesday": "Onsdag", + "thursday": "Torsdag", + "friday": "Fredag", + "saturday": "Lørdag", + "sunday_short": "Søn", + "monday_short": "Man", + "tuesday_short": "Tirs", + "wednesday_short": "Ons", + "thursday_short": "Tors", + "friday_short": "Fre", + "saturday_short": "Lør" } diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json index 7dc23d85..43a49ffd 100644 --- a/client/src/__locales/de.json +++ b/client/src/__locales/de.json @@ -7,12 +7,15 @@ "load_balancing": "Lastverteilung", "load_balancing_desc": "Einen Server nach dem anderen abfragen. AdGuard Home verwendet den gewichteten Zufallsalgorithmus, um den Server so auszuwählen, dass der schnellste Server häufiger verwendet wird.", "bootstrap_dns": "Bootstrap DNS-Server", - "bootstrap_dns_desc": "Bootstrap-DNS-Server werden verwendet, um IP-Adressen der DoH/DoT-Resolver aufzulösen, die Sie als Upstreams angeben.", + "bootstrap_dns_desc": "IP-Adressen der DNS-Server, die zum Auflösen der IP-Adressen von DoH/DoT Upstream-Servern verwendet werden, die Sie angegeben haben. Kommentare sind nicht erlaubt.", + "fallback_dns_title": "Fallback-DNS-Server", + "fallback_dns_desc": "Liste der Fallback-DNS-Server, die verwendet werden, wenn die Upstream-DNS-Server nicht antworten. Die Syntax ist die gleiche wie im Hauptfeld für Upstream-Server oben.", + "fallback_dns_placeholder": "Geben Sie einen Fallback-DNS-Server pro Zeile ein", "local_ptr_title": "Private inverse DNS-Server", "local_ptr_desc": "Die DNS-Server, die AdGuard Home für lokale PTR-Abfragen verwendet. Diese Server werden verwendet, um die Hostnamen von Clients mit privaten IP-Adressen, z. B. „192.168.12.34“, per inverse DNS-Anfragen aufzulösen. Wenn nicht festgelegt, verwendet AdGuard Home die Adressen der Standard-DNS-Auflöser Ihres Betriebssystems mit Ausnahme der Adressen von AdGuard Home selbst.", "local_ptr_default_resolver": "Standardmäßig verwendet AdGuard Home die folgenden Invers-DNS-Resolver: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home konnte keine geeigneten privaten Invers-DNS-Resolver für dieses System ermitteln.", - "local_ptr_placeholder": "Eine Serveradresse pro Zeile eingeben", + "local_ptr_placeholder": "Geben Sie eine IP-Adresse pro Zeile ein", "resolve_clients_title": "Hostnamenauflösung der Clients aktivieren", "resolve_clients_desc": "Inverses Auflösen der IP-Adressen der Clients in ihre Hostnamen durch Senden von PTR-Anfragen an die entsprechenden Resolver (private DNS-Server für lokale Kunden, Upstream-Server für Kunden mit öffentlichen IP-Adressen).", "use_private_ptr_resolvers_title": "Private Reverse-DNS-Resolver verwenden", @@ -125,6 +128,8 @@ "top_clients": "Top Clients", "no_clients_found": "Keine Clients gefunden", "general_statistics": "Allgemeine Statistiken", + "top_upstreams": "Top Upstreams", + "no_upstreams_data_found": "Keine Upstream-Daten gefunden", "number_of_dns_query_days": "Anzahl der in den letzten {{count}} Tagen verarbeiteten DNS-Anfragen", "number_of_dns_query_days_plural": "Anzahl der DNS-Abfragen, die in den letzten {{count}} Tagen verarbeitet wurden", "number_of_dns_query_24_hours": "Anzahl der in den letzten 24 Stunden durchgeführten DNS-Anfragen", @@ -134,6 +139,7 @@ "enforced_save_search": "Sichere Suche erzwungen", "number_of_dns_query_to_safe_search": "Anzahl der DNS-Anfragen bei denen Sichere Suche für Suchanfragen erzwungen wurde", "average_processing_time": "Durchschnittliche Bearbeitungsdauer", + "processing_time": "Verarbeitungszeit", "average_processing_time_hint": "Durchschnittliche Zeit in Millisekunden zur Bearbeitung von DNS-Anfragen", "block_domain_use_filters_and_hosts": "Domains durch Filter und Host-Dateien sperren", "filters_block_toggle_hint": "Sie können Blockierregeln in den Filtereinstellungen erstellen.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Konfiguriert in {{path}}", "test_upstream_btn": "Upstreams testen", "upstreams": "Upstreams", + "upstream": "Upstream", "apply_btn": "Anwenden", "disabled_filtering_toast": "Filtern deaktiviert", "enabled_filtering_toast": "Filtern aktiviert", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: spezieller Wert, <0>A-Datensätze des \n vorgeschalteten Servers beibehalten", "rewrite_AAAA": "<0>AAAA: spezieller Wert, <0>AAAA-Datensätze des vorgeschalteten Servers beibehalten", "disable_ipv6": "IPv6 deaktivieren", - "disable_ipv6_desc": "Löschen Sie alle DNS-Abfragen für IPv6-Adressen (Typ AAAA).", + "disable_ipv6_desc": "Alle DNS-Anfragen für IPv6-Adressen (Typ AAAA) verwerfen und IPv6-Hinweise aus HTTPS-Antworten entfernen.", "fastest_addr": "Schnellste IP-Adresse", "fastest_addr_desc": "Fragen Sie alle DNS-Server ab und geben Sie die schnellste IP-Adresse unter allen Antworten zurück. Dies verlangsamt DNS-Abfragen, da AdGuard Home auf Antworten von allen DNS-Servern warten muss, verbessert jedoch die Gesamtkonnektivität.", "autofix_warning_text": "Wenn Sie auf „Beheben“ klicken, konfiguriert AdGuardHome Ihr System für die Verwendung des AdGuardHome-DNS-Servers.", @@ -676,5 +683,37 @@ "protection_section_label": "Schutz", "log_and_stats_section_label": "Abfrageprotokoll und Statistik", "ignore_query_log": "Diesen Client im Abfrageprotokoll ignorieren", - "ignore_statistics": "Diesen Client in der Statistik ignorieren" + "ignore_statistics": "Diesen Client in der Statistik ignorieren", + "schedule_services": "Dienstblockierung anhalten", + "schedule_services_desc": "Konfigurieren Sie den Pausenplan des Dienstblockierungsfilters", + "schedule_services_desc_client": "Konfigurieren Sie den Pausenplan des Dienstblockierungsfilters für diesen Client", + "schedule_desc": "Inaktivitätszeiträume für blockierte Dienste festlegen", + "schedule_invalid_select": "Die Startzeit muss vor der Endzeit liegen", + "schedule_select_days": "Tage auswählen", + "schedule_timezone": "Wählen Sie eine Zeitzone", + "schedule_current_timezone": "Aktuelle Zeitzone: {{value}}", + "schedule_time_all_day": "Ganzen Tag", + "schedule_modal_description": "Dieser Zeitplan wird alle bestehenden Zeitpläne für denselben Wochentag ersetzen. Jeder Wochentag kann nur einen Inaktivitätszeitraum haben.", + "schedule_modal_time_off": "Keine Dienstblockierung:", + "schedule_new": "Neuer Zeitplan", + "schedule_edit": "Zeitplan bearbeiten", + "schedule_save": "Zeitplan speichern", + "schedule_add": "Zeitplan hinzufügen", + "schedule_remove": "Zeitplan entfernen", + "schedule_from": "Von", + "schedule_to": "Bis", + "sunday": "Sonntag", + "monday": "Montag", + "tuesday": "Dienstag", + "wednesday": "Mittwoch", + "thursday": "Donnerstag", + "friday": "Freitag", + "saturday": "Samstag", + "sunday_short": "So", + "monday_short": "Mo", + "tuesday_short": "Di", + "wednesday_short": "Mi", + "thursday_short": "Do", + "friday_short": "Fr", + "saturday_short": "Sa" } diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 6b73220a..d7ea54a2 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -7,12 +7,15 @@ "load_balancing": "Load-balancing", "load_balancing_desc": "Query one upstream server at a time. AdGuard Home uses its weighted random algorithm to pick the server so that the fastest server is used more often.", "bootstrap_dns": "Bootstrap DNS servers", - "bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams.", + "bootstrap_dns_desc": "IP addresses of DNS servers used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams. Comments are not permitted.", + "fallback_dns_title": "Fallback DNS servers", + "fallback_dns_desc": "List of fallback DNS servers used when upstream DNS servers are not responding. The syntax is the same as in the main upstreams field above.", + "fallback_dns_placeholder": "Enter one fallback DNS server per line", "local_ptr_title": "Private reverse DNS servers", "local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve PTR requests for addresses in private IP ranges, for example \"192.168.12.34\", using reverse DNS. If not set, AdGuard Home uses the addresses of the default DNS resolvers of your OS except for the addresses of AdGuard Home itself.", "local_ptr_default_resolver": "By default, AdGuard Home uses the following reverse DNS resolvers: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home could not determine suitable private reverse DNS resolvers for this system.", - "local_ptr_placeholder": "Enter one server address per line", + "local_ptr_placeholder": "Enter one IP address per line", "resolve_clients_title": "Enable reverse resolving of clients' IP addresses", "resolve_clients_desc": "Reversely resolve clients' IP addresses into their hostnames by sending PTR queries to corresponding resolvers (private DNS servers for local clients, upstream servers for clients with public IP addresses).", "use_private_ptr_resolvers_title": "Use private reverse DNS resolvers", @@ -125,6 +128,8 @@ "top_clients": "Top clients", "no_clients_found": "No clients found", "general_statistics": "General statistics", + "top_upstreams": "Top upstreams", + "no_upstreams_data_found": "No upstreams data found", "number_of_dns_query_days": "The number of DNS queries processed for the last {{count}} day", "number_of_dns_query_days_plural": "The number of DNS queries processed for the last {{count}} days", "number_of_dns_query_24_hours": "The number of DNS queries processed for the last 24 hours", @@ -134,6 +139,7 @@ "enforced_save_search": "Enforced safe search", "number_of_dns_query_to_safe_search": "The number of DNS requests to search engines for which Safe Search was enforced", "average_processing_time": "Average processing time", + "processing_time": "Processing time", "average_processing_time_hint": "Average time in milliseconds on processing a DNS request", "block_domain_use_filters_and_hosts": "Block domains using filters and hosts files", "filters_block_toggle_hint": "You can setup blocking rules in the Filters settings.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Configured in {{path}}", "test_upstream_btn": "Test upstreams", "upstreams": "Upstreams", + "upstream": "Upstream", "apply_btn": "Apply", "disabled_filtering_toast": "Disabled filtering", "enabled_filtering_toast": "Enabled filtering", @@ -530,8 +537,8 @@ "statistics_enable": "Enable statistics", "ignore_domains": "Ignored domains (separated by newline)", "ignore_domains_title": "Ignored domains", - "ignore_domains_desc_stats": "Queries for these domains are not written to the statistics", - "ignore_domains_desc_query": "Queries for these domains are not written to the query log", + "ignore_domains_desc_stats": "Queries matching these rules are not written to the statistics", + "ignore_domains_desc_query": "Queries matching these rules are not written to the query log", "interval_hours": "{{count}} hour", "interval_hours_plural": "{{count}} hours", "filters_configuration": "Filters configuration", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: special value, keep <0>A records from the upstream", "rewrite_AAAA": "<0>AAAA: special value, keep <0>AAAA records from the upstream", "disable_ipv6": "Disable resolving of IPv6 addresses", - "disable_ipv6_desc": "Drop all DNS queries for IPv6 addresses (type AAAA).", + "disable_ipv6_desc": "Drop all DNS queries for IPv6 addresses (type AAAA) and remove IPv6 hints from HTTPS responses.", "fastest_addr": "Fastest IP address", "fastest_addr_desc": "Query all DNS servers and return the fastest IP address among all responses. This slows down DNS queries as AdGuard Home has to wait for responses from all DNS servers, but improves the overall connectivity.", "autofix_warning_text": "If you click \"Fix\", AdGuard Home will configure your system to use AdGuard Home DNS server.", @@ -676,5 +683,37 @@ "protection_section_label": "Protection", "log_and_stats_section_label": "Query log and statistics", "ignore_query_log": "Ignore this client in query log", - "ignore_statistics": "Ignore this client in statistics" + "ignore_statistics": "Ignore this client in statistics", + "schedule_services": "Pause service blocking", + "schedule_services_desc": "Configure the pause schedule of the service-blocking filter", + "schedule_services_desc_client": "Configure the pause schedule of the service-blocking filter for this client", + "schedule_desc": "Set inactivity periods for blocked services", + "schedule_invalid_select": "Start time must be before end time", + "schedule_select_days": "Select days", + "schedule_timezone": "Select a time zone", + "schedule_current_timezone": "Current time zone: {{value}}", + "schedule_time_all_day": "All day", + "schedule_modal_description": "This schedule will replace any existing schedules for the same day of the week. Each day of the week can have only one inactivity period.", + "schedule_modal_time_off": "No service blocking:", + "schedule_new": "New schedule", + "schedule_edit": "Edit schedule", + "schedule_save": "Save schedule", + "schedule_add": "Add schedule", + "schedule_remove": "Remove schedule", + "schedule_from": "From", + "schedule_to": "To", + "sunday": "Sunday", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday_short": "Sun", + "monday_short": "Mon", + "tuesday_short": "Tue", + "wednesday_short": "Wed", + "thursday_short": "Thu", + "friday_short": "Fri", + "saturday_short": "Sat" } diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json index 47502910..9f8e9aff 100644 --- a/client/src/__locales/es.json +++ b/client/src/__locales/es.json @@ -7,12 +7,15 @@ "load_balancing": "Balanceo de carga", "load_balancing_desc": "Consulta un servidor DNS de subida a la vez. AdGuard Home utiliza su algoritmo aleatorio ponderado para elegir el servidor más rápido y sea utilizado con más frecuencia.", "bootstrap_dns": "Servidores DNS de arranque", - "bootstrap_dns_desc": "Los servidores DNS de arranque se utilizan para resolver las direcciones IP de los resolutores DoH/DoT que especifiques como DNS de subida.", + "bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.", + "fallback_dns_title": "Servidores DNS de fallback", + "fallback_dns_desc": "La lista de DNS de fallback serán usadas cuando los servidores de upstream de DNS no respondan. La sintaxis es la misma que en los principales del campo anterior.", + "fallback_dns_placeholder": "Ingresa un servidor de DNS alternativo por línea", "local_ptr_title": "Servidores DNS inversos y privados", "local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver las peticiones PTR de direcciones en rangos de IP privadas, por ejemplo \"192.168.12.34\", utilizando DNS inverso. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.", "local_ptr_default_resolver": "Por defecto, AdGuard Home utiliza los siguientes resolutores DNS inversos: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home no pudo determinar los resolutores DNS inversos y privados adecuados para este sistema.", - "local_ptr_placeholder": "Ingresa una dirección de servidor por línea", + "local_ptr_placeholder": "Ingresa una dirección IP por línea", "resolve_clients_title": "Habilitar la resolución inversa de las direcciones IP de clientes", "resolve_clients_desc": "Resolve de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, servidores DNS de subida para clientes con direcciones IP públicas).", "use_private_ptr_resolvers_title": "Usar resolutores DNS inversos y privados", @@ -125,6 +128,8 @@ "top_clients": "Clientes más frecuentes", "no_clients_found": "No se han encontrado clientes", "general_statistics": "Estadísticas generales", + "top_upstreams": "Mejores upstreams", + "no_upstreams_data_found": "No se han encontrado datos de upstreams", "number_of_dns_query_days": "Número de consultas DNS procesadas durante el último {{count}} día", "number_of_dns_query_days_plural": "Número de consultas DNS procesadas durante los últimos {{count}} días", "number_of_dns_query_24_hours": "Número de consultas DNS procesadas durante las últimas 24 horas", @@ -134,6 +139,7 @@ "enforced_save_search": "Búsquedas seguras forzadas", "number_of_dns_query_to_safe_search": "Número de peticiones DNS a los motores de búsqueda para los que se aplicó la búsqueda segura forzada", "average_processing_time": "Tiempo promedio de procesamiento", + "processing_time": "Tiempo de procesamiento", "average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petición DNS", "block_domain_use_filters_and_hosts": "Bloquear dominios usando filtros y archivos hosts", "filters_block_toggle_hint": "Puedes configurar las reglas de bloqueo en la configuración de filtros.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Configurado en {{path}}", "test_upstream_btn": "Probar DNS de subida", "upstreams": "DNS de subida", + "upstream": "Upstream", "apply_btn": "Aplicar", "disabled_filtering_toast": "Filtrado deshabilitado", "enabled_filtering_toast": "Filtrado habilitado", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: valor especial, mantiene registros <0>A del DNS de subida", "rewrite_AAAA": "<0>AAAA: valor especial, mantiene registros <0>AAAA del DNS de subida", "disable_ipv6": "Deshabilitar resolución de direcciones IPv6", - "disable_ipv6_desc": "Descarta todas las consultas DNS para direcciones IPv6 (tipo AAAA).", + "disable_ipv6_desc": "Descarta todas las consultas de DNS para direcciones IPv6 (tipo AAAA) y elimina las sugerencias de IPv6 de las respuestas HTTPS.", "fastest_addr": "Dirección IP más rápida", "fastest_addr_desc": "Consulta todos los servidores DNS y devuelve la dirección IP más rápida de todas las respuestas. Esto ralentiza las consultas DNS ya que AdGuard Home tiene que esperar las respuestas de todos los servidores DNS, pero mejora la conectividad general.", "autofix_warning_text": "Si haces clic en \"Corregir\", AdGuard Home configurará tu sistema para utilizar el servidor DNS de AdGuard Home.", @@ -676,5 +683,37 @@ "protection_section_label": "Protección", "log_and_stats_section_label": "Registro de consultas y estadísticas", "ignore_query_log": "Ignorar este cliente en el registro de consultas", - "ignore_statistics": "Ignorar este cliente en las estadísticas" + "ignore_statistics": "Ignorar este cliente en las estadísticas", + "schedule_services": "Pausar el servicio de bloqueo", + "schedule_services_desc": "Configura el horario programado de pausa del servicio de bloqueo", + "schedule_services_desc_client": "Configurar el horario programado de pausa del bloqueo de servicio filtrado para este cliente", + "schedule_desc": "Establecer periodos de inactividad para servicios bloqueados", + "schedule_invalid_select": "El tiempo de inicio debe de ir antes del tiempo de finalización", + "schedule_select_days": "Selecciona los dias", + "schedule_timezone": "Selecciona una zona horaria", + "schedule_current_timezone": "Zona horaria actual: {{value}}", + "schedule_time_all_day": "Todo el dia", + "schedule_modal_description": "Este horario sustituirá cualquier horario existente para el mismo día de la semana. Cada día de la semana solo puede tener un periodo de inactividad.", + "schedule_modal_time_off": "Detener el servicio de bloqueo:", + "schedule_new": "Nuevo horario", + "schedule_edit": "Editar horario", + "schedule_save": "Guardar horario", + "schedule_add": "Añadir horario", + "schedule_remove": "Eliminar horario", + "schedule_from": "De", + "schedule_to": "A", + "sunday": "Domingo", + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday_short": "Dom.", + "monday_short": "Lun.", + "tuesday_short": "Mar.", + "wednesday_short": "Mié.", + "thursday_short": "Jue.", + "friday_short": "Vie.", + "saturday_short": "Sáb." } diff --git a/client/src/__locales/fa.json b/client/src/__locales/fa.json index 4f4bf2f0..7ba7b05b 100644 --- a/client/src/__locales/fa.json +++ b/client/src/__locales/fa.json @@ -5,8 +5,12 @@ "upstream_parallel": "استفاده از جستار موازی برای سرعت دادن به تفکیک با جستار همزمان همه جریان های ارسالی", "parallel_requests": "درخواست های موازی", "load_balancing": "متعادل کننده بار", + "load_balancing_desc": "یک سرور بالادستی را در یک زمان پرس و جو کنید. AdGuard Home از الگوریتم تصادفی وزنی خود برای انتخاب سرور استفاده می کند تا سریع ترین سرور بیشتر مورد استفاده قرار گیرد.", "bootstrap_dns": "خودراه انداز سرورهای DNS", - "bootstrap_dns_desc": "خودراه انداز سرورهای DNS برای تفکیک آدرس آی پی تفکیک کننده های DoH/DoT که شما بعنوان جریان ارسالی تعیین کردید استفاده میشود.", + "bootstrap_dns_desc": "آدرس‌های IP سرورهای DNS که برای حل کردن آدرس‌های IP حل‌کننده‌های DoH/DoT که به‌عنوان upstream مشخص می‌کنید، استفاده می‌شوند. اظهار نظر مجاز نیست.", + "fallback_dns_title": "سرورهای DNS بازگشتی", + "fallback_dns_desc": "لیست سرورهای DNS بازگشتی که در زمانی که سرورهای DNS بالادستی پاسخ نمی‌دهند استفاده می‌شوند. نحو مانند فیلد بالادستی اصلی بالا است.", + "fallback_dns_placeholder": "یک سرور DNS بازگشتی در هر خط وارد کنید", "local_ptr_title": "سرورهای خصوصی DNS", "local_ptr_desc": "سرور یا سرور های DNS ای که AdGuard Home برای درخواست های منابع محلی ارائه شده مورد استفاده قرار خواهد داد. برای مثال، این سرور برای تعیین نام های سرویس دهنده برای سرویس گیرنده با آدرس های آی پی خصوصی مورد استفاده قرار خواهد گرفت. اگر تعیین نشود،AdGuard Home به طور خودکار از تعیین کننده ی DNS پیش فرض شما استفاده خواهد کرد.", "local_ptr_default_resolver": "به طور پیش فرض، AdGuard Home از تعیین کننده های DNS معکوس زیر استفاده می کند: {{ip}}.", @@ -20,6 +24,8 @@ "save_config": "ذخیره پیکربندی", "enabled_dhcp": "سرور DHCP فعال شده است", "disabled_dhcp": "سرور DHCP غیرفعال شده است", + "unavailable_dhcp": "DHCP در دسترس نیست", + "unavailable_dhcp_desc": "AdGuard Home نمی تواند سرور DHCP را روی سیستم عامل شما اجرا کند", "dhcp_title": "سرور DHCP", "dhcp_description": "اگر روتر شما تنظیمات DHCP ارائه نمی کند،میتوانید از سرور DHCP تو-کار خود AdGuard استفاده کنید.", "dhcp_enable": "فعالسازی سرور DHCP", @@ -30,6 +36,8 @@ "dhcp_static_leases": "اجاره DHCP ایستا", "dhcp_leases_not_found": "اجاره DHCP یافت نشد", "dhcp_config_saved": "پیکربندی سرور DHCP ذخیره شده است", + "dhcp_ipv4_settings": "تنظیمات DHCP IPv4", + "dhcp_ipv6_settings": "تنظیمات DHCP IPv4", "form_error_required": "فیلد مورد نیاز", "form_error_ip4_format": "فرمت نامعتبر IPv4", "form_error_ip4_gateway_format": "قالب IPv4 درگاه نامعتبر است", @@ -37,8 +45,10 @@ "form_error_ip_format": "فرمت IPv4 نامعتبر است", "form_error_mac_format": "فرمت مَک نامعتبر است", "form_error_client_id_format": "فرمت شناسه کلاینت نامعتبر است", + "form_error_server_name": "نام سرور نامعتبر است", "form_error_subnet": "زیرشبکه\"{{cidr}}\"آدرس آی پی {{ip}} را در بر ندارد", "form_error_positive": "باید بزرگتر از 0 باشد", + "form_error_gateway_ip": "Lease نمی تواند آدرس IP دروازه را داشته باشد", "out_of_range_error": "باید خارج از دامنه باشد\"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "باید کمتر از شروع دامنه باشد", "greater_range_start_error": "باید بیشتر از شروع دامنه باشد", @@ -118,6 +128,8 @@ "top_clients": "بالاترین کلاینت ها", "no_clients_found": "کلاینتی یافت نشد", "general_statistics": "آمار عمومی", + "top_upstreams": "سرورهای بالادست بالا", + "no_upstreams_data_found": "هیچ اطلاعاتی در مورد سرورهای بالادست یافت نشد", "number_of_dns_query_days": "تعداد جستار DNS پردازش شده در {{count}} روز آخر", "number_of_dns_query_days_plural": "تعداد جستار DNS پردازش شده در {{count}} روز گذشته", "number_of_dns_query_24_hours": "تعداد جستار DNS پردازش شده در 24 ساعت گذشته", @@ -127,6 +139,7 @@ "enforced_save_search": "جستجوی اَمن اجبار شده", "number_of_dns_query_to_safe_search": "تعداد درخواست های DNS برای موتور جستجو که جستجوی اَمن اجبار شده", "average_processing_time": "میانگین زمان پردازش", + "processing_time": "زمان پردازش", "average_processing_time_hint": "زمان میانگین بر هزارم ثانیه در پردازش درخواست DNS", "block_domain_use_filters_and_hosts": "مسدودسازی دامنه ها توسط فیلترها و فایل های میزبان", "filters_block_toggle_hint": "میتوانید دستورات مسدودسازی را در تنظیمات فیلترها راه اندازی کنید.", @@ -149,6 +162,7 @@ "upstream_dns": "سرورهای DNS جریان ارسالی", "test_upstream_btn": "تست جریان ارسالی", "upstreams": "جریان ارسالی", + "upstream": "سرور مادر", "apply_btn": "اِعمال", "disabled_filtering_toast": "فیلترینگ غیرفعال شده است", "enabled_filtering_toast": "فیلترینگ فعال شده است", @@ -181,6 +195,8 @@ "new_allowlist": "لیست مجاز جدید", "edit_blocklist": "ویرایش لیست سیاه", "edit_allowlist": "ویرایش لیست مجاز", + "choose_blocklist": "لیست های مسدود را انتخاب کنید", + "choose_allowlist": "لیست های مسدود را انتخاب کنید", "enter_valid_blocklist": "آدرس معتبر برای لیست سیاه وارد کنید.", "enter_valid_allowlist": "آدرس معتبر برای لیست مجاز وارد کنید.", "form_error_url_format": "فرمت آدرس نامعتبر است", @@ -246,6 +262,8 @@ "anonymize_client_ip": "گمنام کردن IP کلاینت", "anonymize_client_ip_desc": "آدرس IP کلاینت در وقایع و آمارها را ذخیره نکن", "dns_config": "پیکربندی DNS سرور", + "dns_cache_config": "تنظیمات کش DNS", + "dns_cache_config_desc": "در اینجا می توانید کش DNS را تنظیم کنید", "blocking_mode": "حالت مسدودسازی", "default": "پيش فرض", "nxdomain": "NXDOMAIN", @@ -514,7 +532,7 @@ "example_rewrite_domain": "فقط بازنویسی پاسخ برای این دامنه.", "example_rewrite_wildcard": "بازنویسی پاسخ ها برای همه زیردامنه های <0>example.org.", "disable_ipv6": "غیرفعالسازی IPv6", - "disable_ipv6_desc": "اگر این ویژگی فعال شده، همه جستارهای DNS برای آدرس های IPv6 (نوع AAAA) رها میشود.", + "disable_ipv6_desc": "تمام درخواست‌های DNS برای آدرس‌های IPv6 را رها کنید (تایپ AAAA) و نکات IPv6 را از پاسخ‌های HTTPS حذف کنید.", "fastest_addr": "سریعترین آدرس آی پی", "fastest_addr_desc": "جستار همه سرورهای DNS و بازگرداندن سریعترین آدرس IP از میان همه پاسخ ها", "autofix_warning_text": "اگر روی \"تعمیر\" کلیک کنید، AdGuardHome سیستم شما را برای استفاده از DNS سرور AdGuardHome پیکربندی می کند.", @@ -572,5 +590,40 @@ "parental_control": "نظارت والدین", "safe_browsing": "وب گردی اَمن", "form_error_password_length": "رمزعبور باید حداقل {{value}} کاراکتر باشد.", - "protection_section_label": "حفاظت" + "protection_section_label": "حفاظت", + "log_and_stats_section_label": "گزارش پرس و جو و آمار", + "ignore_query_log": "این مشتری را در گزارش پرس و جو نادیده بگیرید", + "ignore_statistics": "این مشتری را در آمار نادیده بگیرید", + "schedule_services": "توقف توقف سرویس", + "schedule_services_desc": "برنامه مکث فیلتر مسدودکننده سرویس را پیکربندی کنید", + "schedule_services_desc_client": "برنامه توقف موقت فیلتر مسدودکننده سرویس را برای این سرویس گیرنده پیکربندی کنید", + "schedule_desc": "دوره های عدم فعالیت را برای سرویس های مسدود شده تنظیم کنید", + "schedule_invalid_select": "زمان شروع باید قبل از زمان پایان باشد", + "schedule_select_days": "روزها را انتخاب کنید", + "schedule_timezone": "یک منطقه زمانی را انتخاب کنید", + "schedule_current_timezone": "منطقه زمانی فعلی: {{value}}", + "schedule_time_all_day": "تمام روز", + "schedule_modal_description": "این برنامه جایگزین برنامه‌های موجود برای همان روز هفته خواهد شد. هر روز از هفته می تواند تنها یک دوره عدم فعالیت داشته باشد.", + "schedule_modal_time_off": "بدون مسدود کردن سرویس:", + "schedule_new": "برنامه جدید", + "schedule_edit": "ویرایش برنامه", + "schedule_save": "ذخیره برنامه", + "schedule_add": "برنامه اضافه کنید", + "schedule_remove": "حذف برنامه", + "schedule_from": "از", + "schedule_to": "تا", + "sunday": "یکشنبه", + "monday": "دوشنبه", + "tuesday": "سهشنبه", + "wednesday": "چهار شنبه", + "thursday": "پنج شنبه", + "friday": "جمعه", + "saturday": "شنبه", + "sunday_short": "یکشنبه", + "monday_short": "دوشنبه", + "tuesday_short": "سه شنبه", + "wednesday_short": "چهارشنبه", + "thursday_short": "پنج شنبه", + "friday_short": "جمعه", + "saturday_short": "شنبه" } diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json index f56db735..ae8655ea 100644 --- a/client/src/__locales/fi.json +++ b/client/src/__locales/fi.json @@ -7,12 +7,15 @@ "load_balancing": "Kuormantasaus", "load_balancing_desc": "Lähetä pyyntö yhdelle ylävirtapalvelimelle kerrallaan. AdGuard Home pyrkii valitsemaan nopeimman palvelimen painotetun satunnaisalgoritminsa avulla.", "bootstrap_dns": "Bootstrap DNS-palvelimet", - "bootstrap_dns_desc": "Bootstrap DNS-palvelimia käytetään ylävirroiksi määritettyjen DoH/DoT-resolvereiden IP-osoitteiden selvitykseen.", + "bootstrap_dns_desc": "Ylävirroiksi määrittämiesi DoH/DoT-resolverien IP-osoitteiden selvitykseen käytettävien DNS-palvelimien IP-osoitteet. Kommentteja ei sallita.", + "fallback_dns_title": "DNS-varapalvelimet", + "fallback_dns_desc": "Listaus DNS-varapalvelimista, joita käytetään kun lähtevät DNS-palvelimet eivät vastaa. Syntaksi on sama kuin yllä olevassa pääylävirrat-kentässä.", + "fallback_dns_placeholder": "Syötä yksi DNS-varapalvelin per rivi", "local_ptr_title": "Yksityiset käänteis-DNS-palvelimet", "local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-pyynnöille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien PTR-pyyntöjen osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteis-DNS:n avulla. Jos ei käytössä, AdGuard Home käyttää käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.", "local_ptr_default_resolver": "Oletusarvoisesti AdGuard Home käyttää seuraavia käänteis-DNS-resolvereita: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home ei voinut määrittää tälle järjestelmälle sopivaa yksityistä käänteis-DNS-resolveria.", - "local_ptr_placeholder": "Syötä yksi palvelimen osoite per rivi", + "local_ptr_placeholder": "Syötä yksi IP-osoite per rivi", "resolve_clients_title": "Käytä päätelaitteiden IP-osoitteille käänteistä selvitystä", "resolve_clients_desc": "Selvitä päätelaitteiden IP-osoitteiden isäntänimet käänteisesti lähettämällä PTR-pyynnöt sopiville resolvereille (yksityiset DNS-palvelimet paikallisille päätelaitteille, ylävirtapalvelimet päätelaitteille, joilla on julkiset IP-osoitteet).", "use_private_ptr_resolvers_title": "Käytä yksityisiä käänteis-DNS-resolvereita", @@ -125,6 +128,8 @@ "top_clients": "Käytetyimmät päätelaitteet", "no_clients_found": "Päätelaitteita ei löytynyt", "general_statistics": "Yleiset tilastot", + "top_upstreams": "Käytetyimmät ylävirrat", + "no_upstreams_data_found": "Ylävirtatietoja ei löytynyt", "number_of_dns_query_days": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten {{count}} päivän ajalta", "number_of_dns_query_days_plural": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten {{count}} päivän ajalta", "number_of_dns_query_24_hours": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten 24 tunnin ajalta", @@ -134,6 +139,7 @@ "enforced_save_search": "Turvallinen haku pakotettiin", "number_of_dns_query_to_safe_search": "DNS-pyyntöjen määrä, joille turvallinen haku pakotettiin käyttöön", "average_processing_time": "Keskimääräinen käsittelyaika", + "processing_time": "Käsittelyaika", "average_processing_time_hint": "Keskimääräinen DNS-pyynnön käsittelyyn kulutettu aika millisekunteina", "block_domain_use_filters_and_hosts": "Estä verkkotunnuksia suodattimilla ja hosts-tiedostoilla", "filters_block_toggle_hint": "Voit määrittää estosääntöjä suodatinasetuksissa.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Määritetty tiedostossa {{path}}", "test_upstream_btn": "Testaa ylävirtoja", "upstreams": "Ylävirrat", + "upstream": "Ylävirta", "apply_btn": "Käytä", "disabled_filtering_toast": "Suodatus poistettiin käytöstä", "enabled_filtering_toast": "Suodatus otettiin käyttöön", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: erityinen arvo, säilytä ylävirran <0>A-tiedot", "rewrite_AAAA": "<0>AAAA: erityinen arvo, säilytä ylävirran <0>AAAA-tiedot", "disable_ipv6": "Älä selvitä IPv6-osoitteita", - "disable_ipv6_desc": "Hylkää kaikki IPv6-osoitteiden DNS-pyynnöt (tyyppi AAAA).", + "disable_ipv6_desc": "Hylkää kaikki IPv6-osoitteiden DNS-pyynnöt (tyyppi AAAA) ja poista HTTPS-vastausten IPv6-tiedot.", "fastest_addr": "Nopein IP-osoite", "fastest_addr_desc": "Lähetä pyynnöt kaikille DNS-palvelimille ja valitse vastauksista nopein IP-osoite. Tämä parantaa yleistä liitettävyyttä, joskin hidastaa DNS-pyyntöjä, koska AdGuard Homen on odotettava kaikkien DNS-palvelinten vastauksia.", "autofix_warning_text": "Jos painat \"Korjaa\", AdGuard Home määrittää järjestelmäsi käyttämään AdGuard Homen DNS-palvelinta.", @@ -676,5 +683,37 @@ "protection_section_label": "Suojaus", "log_and_stats_section_label": "Kyselyhistoria ja tilastot", "ignore_query_log": "Älä huomioi tätä päätettä kyselyhistoriassa", - "ignore_statistics": "Älä huomioi tätä päätettä tilastoissa" + "ignore_statistics": "Älä huomioi tätä päätettä tilastoissa", + "schedule_services": "Keskeytä palveluesto", + "schedule_services_desc": "Määritä palvelunestosuodattimen keskeytysajoitus.", + "schedule_services_desc_client": "Määritä palvelunestosuodattimen keskeytysajoitus tälle päätteelle.", + "schedule_desc": "Aseta estettujen palveluiden käyttämättömyysjaksot", + "schedule_invalid_select": "Aloitusaika on oltava ennen lopetusaikaa", + "schedule_select_days": "Valitse päivät", + "schedule_timezone": "Valitse aikavyöhyke", + "schedule_current_timezone": "Nykyinen aikavyöhyke: {{value}}", + "schedule_time_all_day": "Koko päivän", + "schedule_modal_description": "Tämä ajoitus korvaa kaikki nykyiset kyseisen viikonpäivän ajoitukset. Jokaisella viikonpäivällä voi olla vain yksi toimettomuusjakso.", + "schedule_modal_time_off": "Ei palveluestoa:", + "schedule_new": "Uusi ajoitus", + "schedule_edit": "Muokkaa ajoitus", + "schedule_save": "Tallenna ajoitus", + "schedule_add": "Lisää ajoitus", + "schedule_remove": "Poista ajoitus", + "schedule_from": "Alkaen", + "schedule_to": "Päättyen", + "sunday": "Sunnuntai", + "monday": "Maanantai", + "tuesday": "Tiistai", + "wednesday": "Keskiviikko", + "thursday": "Torstai", + "friday": "Perjantai", + "saturday": "Lauantai", + "sunday_short": "Su", + "monday_short": "Ma", + "tuesday_short": "Ti", + "wednesday_short": "Ke", + "thursday_short": "To", + "friday_short": "Pe", + "saturday_short": "La" } diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json index d69f9762..738baa9d 100644 --- a/client/src/__locales/fr.json +++ b/client/src/__locales/fr.json @@ -3,16 +3,19 @@ "example_upstream_reserved": "un amont <0>pour des domaines spécifiques ;", "example_upstream_comment": " un commentaire.", "upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.", - "parallel_requests": "Demandes en parallèle", + "parallel_requests": "Requêtes en parallèle", "load_balancing": "Équilibrage de charge", "load_balancing_desc": "Interroger un serveur en amont à la fois. AdGuard Home utilise son algorithme aléatoire pondéré pour choisir le serveur de sorte que le serveur le plus rapide soit utilisé plus souvent.", "bootstrap_dns": "Serveurs DNS d'amorçage", - "bootstrap_dns_desc": "Les serveurs DNS d'amorçage sont utilisés pour résoudre les adresses IP des résolveurs DoH/DoT que vous spécifiez comme upstream.", + "bootstrap_dns_desc": "Les adresses IP des serveurs DNS utilisées pour résoudre les adresses IP des résolveurs DoH/DoT que vous spécifiez comme en amont. Les commentaires ne sont pas autorisés.", + "fallback_dns_title": "Serveurs DNS de repli", + "fallback_dns_desc": "Liste des serveurs DNS de repli utilisés lorsque les serveurs DNS en amont ne répondent pas. La syntaxe est la même que dans le champ principal en amont ci-dessus.", + "fallback_dns_placeholder": "Saisissez un serveur DNS de repli par ligne", "local_ptr_title": "Serveurs DNS privés inverses", "local_ptr_desc": "Les serveurs DNS que AdGuard Home utilise pour les requêtes PTR locales. Ces serveurs sont utilisés pour résoudre les noms d'hôte des clients avec des adresses IP privées, par exemple « 192.168.12.34 », en utilisant le DNS inversé. Si ce paramètre n'est pas défini, AdGuard Home utilise les adresses des résolveurs DNS par défaut de votre système d'exploitation, à l'exception des adresses d'AdGuard Home lui-même.", "local_ptr_default_resolver": "Par défaut, AdGuard Home utilise les résolveurs DNS inversés suivants : {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home n'a pas pu déterminer de résolveurs DNS inversés privés appropriés pour ce système.", - "local_ptr_placeholder": "Saisissez une adresse de serveur par ligne", + "local_ptr_placeholder": "Saisissez une adresse IP par ligne", "resolve_clients_title": "Activer la résolution inverse des adresses IP des clients", "resolve_clients_desc": "Résoudre inversement les adresses IP des clients en leurs noms d'hôtes en envoyant des requêtes PTR aux résolveurs correspondants (serveurs DNS privés pour les clients locaux, serveurs en amont pour les clients ayant une adresse IP publique).", "use_private_ptr_resolvers_title": "Utiliser des résolveurs DNS inversés privés", @@ -125,6 +128,8 @@ "top_clients": "Meilleurs clients", "no_clients_found": "Pas de clients trouvés", "general_statistics": "Statistiques générales", + "top_upstreams": "Top amonts", + "no_upstreams_data_found": "Aucune donnée en amont trouvée", "number_of_dns_query_days": "Le nombre de requêtes DNS traitées pour les {{count}} derniers jours", "number_of_dns_query_days_plural": "Le nombre de requêtes DNS traitées ces {{count}} derniers jours", "number_of_dns_query_24_hours": "Le nombre de requêtes DNS traitées au cours des 24 dernières heures", @@ -134,6 +139,7 @@ "enforced_save_search": "Recherche sécurisée forcée", "number_of_dns_query_to_safe_search": "Le nombre de requêtes DNS faites avec la Recherche securisée", "average_processing_time": "Temps moyen de traitement", + "processing_time": "Délai de traitement", "average_processing_time_hint": "Temps moyen (en millisecondes) de traitement d'une requête DNS", "block_domain_use_filters_and_hosts": "Bloquez les domaines à l'aide des filtres et fichiers hosts", "filters_block_toggle_hint": "Vous pouvez configurer les règles de filtrage dans les paramètres des Filtres.", @@ -149,7 +155,7 @@ "dns_blocklists": "Listes de blocage DNS", "dns_allowlists": "Listes d’autorisation DNS", "dns_blocklists_desc": "AdGuard Home bloquera les domaines correspondant aux listes de blocage.", - "dns_allowlists_desc": "Les domaines provenant de listes d’autorisation DNS seront autorisés même s’ils figurent dans l’une des listes de blocage.", + "dns_allowlists_desc": "Les domaines des listes blanches des DNS seront autorisés même s’ils figurent dans une des listes de blocage.", "custom_filtering_rules": "Règles de filtrage personnalisées", "encryption_settings": "Paramètres de chiffrement", "dhcp_settings": "Paramètres DHCP", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Configuré dans {{path}}", "test_upstream_btn": "Tester les upstreams", "upstreams": "En amont", + "upstream": "Amont", "apply_btn": "Appliquer", "disabled_filtering_toast": "Filtrage désactivé", "enabled_filtering_toast": "Filtrage activé", @@ -191,8 +198,8 @@ "new_allowlist": "Nouvelle liste d’autorisation", "edit_blocklist": "Modifier la liste de blocage", "edit_allowlist": "Modifier la liste d’autorisation", - "choose_blocklist": "Choisir des listes de blocage", - "choose_allowlist": "Choisir des listes d’autorisation", + "choose_blocklist": "Choisir les listes de blocage", + "choose_allowlist": "Choisir les listes blanches", "enter_valid_blocklist": "Saisissez une URL valide vers la liste de blocage.", "enter_valid_allowlist": "Saisissez une URL valide vers la liste d’autorisation.", "form_error_url_format": "Format d’URL incorrect", @@ -304,7 +311,7 @@ "theme_auto": "Auto", "theme_light": "Thème clair", "theme_dark": "Thème sombre", - "upstream_dns_client_desc": "Si vous laissez ce champ vide, AdGuard Home utilisera les serveurs configurés dans les <0>paramètres DNS.", + "upstream_dns_client_desc": "Si vous laissez ce champ vide, AdGuard Home utilisera les serveurs configurés dans les <0>Paramètres DNS.", "tracker_source": "Source du traceur", "source_label": "Source", "found_in_known_domain_db": "Trouvé dans la base de données des domaines connus", @@ -485,7 +492,7 @@ "rewrite_confirm_delete": "Voulez-vous vraiment supprimer la réécriture DNS pour « {{key}} » ?", "rewrite_desc": "Permet de configurer facilement la réponse DNS personnalisée pour un nom de domaine spécifique.", "rewrite_applied": "Règle de réécriture appliquée", - "rewrite_hosts_applied": "Réécrit par la règle du fichier d’hôtes", + "rewrite_hosts_applied": "Réécrit par la règle du fichier hosts", "dns_rewrites": "Réécritures DNS", "form_domain": "Saisissez un domaine ou caracrtère générique", "form_answer": "Saisissez une adresse IP ou un nom de domaine", @@ -552,7 +559,7 @@ "network": "Réseau", "descr": "Description", "whois": "WHOIS", - "filtering_rules_learn_more": "<0>Apprenez-en plus à propos de la création de vos propres listes de blocage d’hôtes.", + "filtering_rules_learn_more": "<0>Apprenez plus sur la création de vos propres listes hosts.", "blocked_by_response": "Bloqué par un CNAME ou une réponse IP", "blocked_by_cname_or_ip": "Bloqué par CNAME ou adresse IP", "try_again": "Réessayer", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A : valeur spéciale, conserver les enregistrements <0>A de l’amont", "rewrite_AAAA": "<0>AAAA : valeur spéciale, conserver les enregistrements <0>AAAA de l’amont", "disable_ipv6": "Désactiver la résolution des adresses IPv6", - "disable_ipv6_desc": "Abandonner toutes les requêtes DNS pour les adresses IPv6 (type AAAA).", + "disable_ipv6_desc": "Supprimer toutes les requêtes DNS pour les adresses IPv6 (type AAAA) et supprimer les indices IPv6 des réponses HTTPS.", "fastest_addr": "Adresse IP la plus rapide", "fastest_addr_desc": "Rechercher tous les serveurs DNS et renvoyer l’adresse IP la plus rapide parmi toutes les réponses. Cela ralentit les requêtes DNS car AdGuard Home doit attendre les réponses de tous les serveurs DNS, mais la connectivité globale s'améliore.", "autofix_warning_text": "Si vous cliquez sur « Réparer », AdGuard Home configurera votre système pour utiliser le serveur DNS AdGuard Home.", @@ -638,7 +645,7 @@ "filter_category_other_desc": "Autres listes noires", "setup_config_to_enable_dhcp_server": "Configurer les paramètres pour activer le serveur DHCP", "original_response": "Réponse originale", - "click_to_view_queries": "Cliquez pour voir les requêtes", + "click_to_view_queries": "Cliquer pour voir les requêtes", "port_53_faq_link": "Le port 53 est souvent occupé par les services « DNSStubListener » ou « systemd-resolved ». Veuillez lire <0>cette instruction pour savoir comment résoudre ce problème.", "adg_will_drop_dns_queries": "AdGuard Home ignorera toutes les requêtes DNS de ce client.", "filter_allowlist": "ATTENTION : Cette action exclura également la règle « {{disallowed_rule}} » de la liste des clients autorisés.", @@ -676,5 +683,37 @@ "protection_section_label": "Protection", "log_and_stats_section_label": "Journal des requêtes et statistiques", "ignore_query_log": "Ignorer ce client dans le journal des requêtes", - "ignore_statistics": "Ignorer ce client dans les statistiques" + "ignore_statistics": "Ignorer ce client dans les statistiques", + "schedule_services": "Suspendre le blocage des services", + "schedule_services_desc": "Configurez l'horaire de pauses du filtre de blocage de services", + "schedule_services_desc_client": "Configurez l'horaire de pauses du filtre de blocage de services pour ce client", + "schedule_desc": "Définissez les périodes d'inactivité pour les services bloqués", + "schedule_invalid_select": "Le temps du début doit précéder le temps de la fin", + "schedule_select_days": "Sélectionnez les jours", + "schedule_timezone": "Sélectionnez un fuseau horaire", + "schedule_current_timezone": "Fuseau horaire actuel : {{value}}", + "schedule_time_all_day": "Toute la journée", + "schedule_modal_description": "Cet horaire remplacera tout autre planning existant pour le même jour de la semaine. Chaque jour de la semaine ne peut avoir qu'une seule période d'inactivité.", + "schedule_modal_time_off": "Suspendre le blocage :", + "schedule_new": "Nouvel horaire", + "schedule_edit": "Modifier l'horaire", + "schedule_save": "Enregistrer l'horaire", + "schedule_add": "Ajouter un horaire", + "schedule_remove": "Supprimer l'horaire", + "schedule_from": "Du", + "schedule_to": "Au", + "sunday": "Dimanche", + "monday": "Lundi", + "tuesday": "Mardi", + "wednesday": "Mercredi", + "thursday": "Jeudi", + "friday": "Vendredi", + "saturday": "Samedi", + "sunday_short": "Dim.", + "monday_short": "Lun.", + "tuesday_short": "Mar.", + "wednesday_short": "Mer.", + "thursday_short": "Jeu.", + "friday_short": "Ven.", + "saturday_short": "Sam." } diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json index feb50c0b..eb9bb9d6 100644 --- a/client/src/__locales/hr.json +++ b/client/src/__locales/hr.json @@ -7,7 +7,10 @@ "load_balancing": "Load-balancing", "load_balancing_desc": "Pitajte jedan po jedan uzvodni poslužitelj. AdGuard Home koristi svoj ponderirani slučajni algoritam za odabir poslužitelja tako da se najbrži poslužitelj koristi češće.", "bootstrap_dns": "Bootstrap DNS poslužitelji", - "bootstrap_dns_desc": "Bootstrap DNS poslužitelji koriste se za rezolvanje IP adresa DoH/DoT rezolvera koje navedete kao upstreams.", + "bootstrap_dns_desc": "IP adrese DNS poslužitelja koji se koriste za rješavanje IP adresa DoH/DoT razrjeđivača koje navedete kao uzvodne. Komentari nisu dopušteni.", + "fallback_dns_title": "Rezervni DNS poslužitelji", + "fallback_dns_desc": "Popis rezervnih DNS poslužitelja koji se koriste kada uzvodni DNS poslužitelji ne odgovaraju. Sintaksa je ista kao u gornjem polju glavnog uzvodnog toka.", + "fallback_dns_placeholder": "Unesite jedan rezervni DNS poslužitelj po retku", "local_ptr_title": "Privatni obrnuti DNS poslužitelji", "local_ptr_desc": "DNS poslužitelji koje AdGuard Home koristi za lokalne PTR upite. Ti se poslužitelji koriste za razrješavanje naziva glavnog računala klijenata s privatnim IP adresama, na primjer \"192.168.12.34\", koristeći obrnuti DNS. Ako nije postavljeno, AdGuard Home koristi adrese zadanih DNS razrješivača vašeg OS-a, osim za adrese samog AdGuard Homea.", "local_ptr_default_resolver": "Prema zadanim postavkama AdGuard Home koristi sljedeće obrnute DNS razrješivače: {{ip}}.", @@ -125,6 +128,8 @@ "top_clients": "Top klijenti", "no_clients_found": "Nema pronađenih klijenata", "general_statistics": "Opća statistika", + "top_upstreams": "Top upstream poslužitelji", + "no_upstreams_data_found": "Nema podataka o upstream poslužiteljima", "number_of_dns_query_days": "Broj DNS upita obrađenih u posljednja {{count}} dan", "number_of_dns_query_days_plural": "Broj DNS upita obrađenih u posljednja {{count}} dana", "number_of_dns_query_24_hours": "Broj DNS upita obrađenih u posljednja 24 sata", @@ -134,6 +139,7 @@ "enforced_save_search": "Omogućeno sigurno pretraživanje", "number_of_dns_query_to_safe_search": "Broj DNS zahtjeva prema pretraživačima za koje je omogućeno Sigurno pretraživanje", "average_processing_time": "Prosječno vrijeme obrade", + "processing_time": "Vrijeme obrade", "average_processing_time_hint": "Prosječno vrijeme u milisekundama za obradu DNS zahtjeva", "block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtre ili hosts datoteke", "filters_block_toggle_hint": "Pravila blokiranja možete postaviti u postavkama filtara.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Postavljeno u {{path}}", "test_upstream_btn": "Testiraj upstream-ove", "upstreams": "Upstreams", + "upstream": "Upstream poslužitelj", "apply_btn": "Primijeni", "disabled_filtering_toast": "Onemogućeno filtriranje", "enabled_filtering_toast": "Omogućeno filtriranje", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: posebna vrijednost, ukloni <0>A zapis od upstreama", "rewrite_AAAA": "<0>AAAA: posebna vrijednost, ukloni <0>AAAA zapis od upstreama", "disable_ipv6": "Onemogući razrješavanje IPv6 adresa", - "disable_ipv6_desc": "Ispustite sve DNS upite za IPv6 adrese (upišite AAAA).", + "disable_ipv6_desc": "Ignorirajte sve DNS zahtjeve adresa IPv6 (tipa AAAA) i uklonite IPv6 podatke iz HTTPS odgovora.", "fastest_addr": "Najbrža IP adresa", "fastest_addr_desc": "Ispitajte sve DNS poslužitelje i vratite najbržu IP adresu među svim odgovorima. To usporava DNS upite jer AdGuard Home mora čekati odgovore svih DNS poslužitelja, ali poboljšava ukupnu povezanost.", "autofix_warning_text": "Ako pritisnete \"Popravi\", AdGuard Home će postaviti vaš sustav da koristi AdGuardHome DNS poslužitelj.", @@ -676,5 +683,37 @@ "protection_section_label": "Zaštita", "log_and_stats_section_label": "Zapisnik upita i statistika", "ignore_query_log": "Zanemari ovog klijenta u zapisniku upita", - "ignore_statistics": "Ignorirajte ovog klijenta u statistici" + "ignore_statistics": "Ignorirajte ovog klijenta u statistici", + "schedule_services": "Pauziraj blokiranje servisa", + "schedule_services_desc": "Konfiguriranje rasporeda pauziranja filtra za blokiranje servisa", + "schedule_services_desc_client": "Konfiguriranje rasporeda pauziranja filtra za blokiranje servisa za ovog klijenta", + "schedule_desc": "Postavljanje razdoblja neaktivnosti za blokirane servise", + "schedule_invalid_select": "Vrijeme početka mora biti prije vremena završetka", + "schedule_select_days": "Odabir dana", + "schedule_timezone": "Odabir vremenske zone", + "schedule_current_timezone": "Trenutna vremenska zona: {{value}}", + "schedule_time_all_day": "Cijeli dan", + "schedule_modal_description": "Ovaj raspored zamijenit će sve postojeće rasporede za isti dan u tjednu. Svaki dan u tjednu može imati samo jedno razdoblje neaktivnosti.", + "schedule_modal_time_off": "Blokiranje usluga je onemogućeno:", + "schedule_new": "Novi raspored", + "schedule_edit": "Uredi raspored", + "schedule_save": "Spremi raspored", + "schedule_add": "Dodaj raspored", + "schedule_remove": "Ukloni raspored", + "schedule_from": "Od", + "schedule_to": "Do", + "sunday": "Nedjelja", + "monday": "Ponedjeljak", + "tuesday": "Utorak", + "wednesday": "Srijeda", + "thursday": "Četvrtak", + "friday": "Petak", + "saturday": "Subota", + "sunday_short": "Ned", + "monday_short": "Pon", + "tuesday_short": "Uto", + "wednesday_short": "Sri", + "thursday_short": "Čet", + "friday_short": "Pet", + "saturday_short": "Sub" } diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json index 37e5cdfe..b94af27f 100644 --- a/client/src/__locales/hu.json +++ b/client/src/__locales/hu.json @@ -7,12 +7,15 @@ "load_balancing": "Terheléselosztás", "load_balancing_desc": "Egyszerre csak egy szerverről történjen lekérdezés. Az AdGuard Home egy súlyozott, véletlenszerű algoritmust fog használni a megfelelő szerver kiválasztására, így a leggyorsabb szervert fogja a leggyakrabban használni.", "bootstrap_dns": "Bootstrap DNS kiszolgálók", - "bootstrap_dns_desc": "A Bootstrap DNS szerverek a DoH/DoT feloldók IP-címeinek feloldására szolgálnak.", + "bootstrap_dns_desc": "A DNS-kiszolgálók IP-címei, amelyek a DoH/DoT-feloldók IP-címeinek feloldására szolgálnak, amelyeket upstreamként megadott. Megjegyzések nem megengedettek.", + "fallback_dns_title": "Tartalék DNS-szerverek", + "fallback_dns_desc": "Azoknak a tartalék DNS-szervereknek a listája, amelyeket akkor használnak, ha a felsőbbrendű DNS-szerverek nem válaszolnak. A szintaxis ugyanaz, mint a fő felsőbbrendű mezőben.", + "fallback_dns_placeholder": "Adjon meg egy alternatív DNS szervert soronként", "local_ptr_title": "Privát DNS szerverek", "local_ptr_desc": "Azok a DNS szerverek, amiket az AdGuard Home a helyi PTR kérésekhez használ. ", "local_ptr_default_resolver": "Alapesetben az AdGuard Home a következő reverse DNS feloldókat használja: {{ip}}.", "local_ptr_no_default_resolver": "Az AdGuard Home nem tudta meghatározni a privát reverse DNS feloldókat ehhez a rendszerhez.", - "local_ptr_placeholder": "Adjon meg soronként egy kiszolgáló címet", + "local_ptr_placeholder": "Adjon meg egy IP-címet soronként", "resolve_clients_title": "Kliensek IP címeinek fordított feloldása", "resolve_clients_desc": "Fordítva oldja fel a kliensek IP címeit a hosztneveikre azáltal, hogy PTR lekérdezéseket küld a megfelelő feloldóknak (privát DNS szerverek a helyi kliensek számára, upstream szerverek a nyilvános IP címmel rendelkező kliensek számára).", "use_private_ptr_resolvers_title": "Privát reverse DNS feloldók használata", @@ -125,6 +128,8 @@ "top_clients": "Legaktívabb kliensek", "no_clients_found": "Nem található kliens", "general_statistics": "Általános statisztikák", + "top_upstreams": "Top upstream szerverek", + "no_upstreams_data_found": "Nem található upstream szerver adat", "number_of_dns_query_days": "Lekérdezések száma az utolsó {{count}} napban", "number_of_dns_query_days_plural": "Feldolgozott DNS lekérdezések száma az utolsó {{count}} napban", "number_of_dns_query_24_hours": "Az elmúlt 24 órában feldolgozott DNS lekérdezések száma", @@ -134,6 +139,7 @@ "enforced_save_search": "Kényszerített biztonságos keresés", "number_of_dns_query_to_safe_search": "A biztonságos keresésre kényszerített DNS lekérdezések száma", "average_processing_time": "Átlagos feldolgozási idő", + "processing_time": "Feldolgozási idő", "average_processing_time_hint": "A DNS lekérdezések feldolgozásához szükséges átlagos idő milliszekundumban", "block_domain_use_filters_and_hosts": "Domainek blokkolása szűrők és hosztfájlok használatával", "filters_block_toggle_hint": "A szűrőbeállításoknál megadhatja a blokkolási szabályokat.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Beállítva itt: {{path}}", "test_upstream_btn": "Upstreamek tesztelése", "upstreams": "Upstream-ek", + "upstream": "Upstream szerver", "apply_btn": "Alkalmaz", "disabled_filtering_toast": "Szűrés letiltva", "enabled_filtering_toast": "Szűrés engedélyezve", @@ -493,7 +500,7 @@ "form_error_answer_format": "Érvénytelen válasz formátum", "configure": "Beállítás", "main_settings": "Fő beállítások", - "block_services": "Speciális szolgáltatások blokkolása", + "block_services": "Adott szolgáltatások blokkolása", "blocked_services": "Blokkolt szolgáltatások", "blocked_services_desc": "Lehetővé teszi a népszerű oldalak és szolgáltatások blokkolását.", "blocked_services_saved": "Blokkolt szolgáltatások sikeresen mentve", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: speciális érték, megtartja az upstream felől érkező <0>A rekordokat", "rewrite_AAAA": "<0>AAAA: speciális érték, megtartja az upstream felől érkező <0>AAAA rekordokat", "disable_ipv6": "IPv6 címek feloldásának tiltása", - "disable_ipv6_desc": "Összes DNS kérés eldobása az IPv6 címekhez (AAAA típus).", + "disable_ipv6_desc": "Dobja el az IPv6-címekre vonatkozó összes DNS-lekérdezést (AAAA típusú), és távolítsa el az IPv6-tippeket a HTTPS-válaszokból.", "fastest_addr": "Leggyorsabb IP-cím", "fastest_addr_desc": "Kérdezze le az összes DNS-kiszolgálót, és adja vissza a leggyorsabb IP-címet a válaszok közül. Ez lelassítja a DNS-lekérdezéseket, mivel az összes DNS-kiszolgáló esetében meg kell várnunk a válaszokat, de javítja az összeköttetést.", "autofix_warning_text": "Ha a \"Javítás\" lehetőségre kattint, az AdGuard Home megpróbálja beállítani a rendszerét, hogy használja az AdGuard Home DNS szervert.", @@ -676,5 +683,37 @@ "protection_section_label": "Védelem", "log_and_stats_section_label": "Lekérdezési napló és statisztikák", "ignore_query_log": "Figyelmen kívül hagyja ezt az ügyfelet a lekérdezési naplóban", - "ignore_statistics": "Hagyja figyelmen kívül ezt az ügyfelet a statisztikákban" + "ignore_statistics": "Hagyja figyelmen kívül ezt az ügyfelet a statisztikákban", + "schedule_services": "A szolgáltatás blokkolásának szüneteltetése", + "schedule_services_desc": "Állítsa be a szolgáltatásblokkoló szűrő szüneteltetési ütemezését", + "schedule_services_desc_client": "Állítsa be a szolgáltatásblokkoló szűrő szüneteltetési ütemezését ehhez az ügyfélhez", + "schedule_desc": "Inaktivitási időszakok beállítása a blokkolt szolgáltatásokhoz", + "schedule_invalid_select": "A kezdési időpontnak a befejezési időpont előtt kell lennie", + "schedule_select_days": "Napok kiválasztása", + "schedule_timezone": "Válasszon időzónát", + "schedule_current_timezone": "Jelenlegi időzóna: {{value}}", + "schedule_time_all_day": "Egész nap", + "schedule_modal_description": "Ez az ütemezés felváltja a hét ugyanazon napjára vonatkozó meglévő ütemezéseket. A hét minden napján csak egy inaktivitási időszak lehet.", + "schedule_modal_time_off": "Nincs szolgáltatás blokkolás:", + "schedule_new": "Új ütemezés", + "schedule_edit": "Ütemezés szerkesztése", + "schedule_save": "Ütemezés mentése", + "schedule_add": "Ütemezés hozzáadása", + "schedule_remove": "Ütemezés eltávolítása", + "schedule_from": "Ettől:", + "schedule_to": "Eddig:", + "sunday": "Vasárnap", + "monday": "Hétfő", + "tuesday": "Kedd", + "wednesday": "Szerda", + "thursday": "Csütörtök", + "friday": "Péntek", + "saturday": "Szombat", + "sunday_short": "Vas", + "monday_short": "Hét", + "tuesday_short": "Kedd", + "wednesday_short": "Szer", + "thursday_short": "Csüt", + "friday_short": "Pén", + "saturday_short": "Szom" } diff --git a/client/src/__locales/id.json b/client/src/__locales/id.json index c83b9581..b0ae2ff8 100644 --- a/client/src/__locales/id.json +++ b/client/src/__locales/id.json @@ -7,7 +7,7 @@ "load_balancing": "Penyeimbang beban", "load_balancing_desc": "Permintaan satu server pada satu waktu. AdGuard Home akan menggunakan algoritma acak tertimbang untuk memilih server sehingga server tercepat akan lebih sering digunakan.", "bootstrap_dns": "Server DNS bootstrap", - "bootstrap_dns_desc": "Server Bootstrap DNS dapat digunakan untuk meresolve alamat IP pada DoH/DoT resolvers yang Anda tentukan sebagai upstreams.", + "bootstrap_dns_desc": "Alamat IP server DNS yang digunakan untuk menyelesaikan alamat IP resolver DoH/DoT yang Anda tentukan sebagai upstream. Komentar tidak diizinkan.", "local_ptr_title": "Server pembalik DNS pribadi", "local_ptr_desc": "Server DNS yang digunakan AdGuard Home untuk kueri PTR lokal. Server ini digunakan untuk menyelesaikan nama host klien dengan alamat IP pribadi, misalnya \"192.168.12.34\", menggunakan DNS terbalik. Jika tidak disetel, AdGuard Home menggunakan alamat resolver DNS default OS Anda kecuali untuk alamat AdGuard Home itu sendiri.", "local_ptr_default_resolver": "Secara bawaan, AdGuard Home menggunakan pemecah DNS terbalik: {{ip}}.", @@ -125,6 +125,8 @@ "top_clients": "Klien teratas", "no_clients_found": "Tidak ditemukan klien", "general_statistics": "Statistik umum", + "top_upstreams": "Top servers upstream", + "no_upstreams_data_found": "Tidak ada data server upstream yang ditemukan", "number_of_dns_query_days": "Jumlah kueri DNS diproses selama {{value}} hari terakhir", "number_of_dns_query_days_plural": "Jumlah kueri DNS yang diproses selama {{count}} hari terakhir", "number_of_dns_query_24_hours": "Jumlah kueri DNS diproses selama 24 jam terakhir", @@ -158,6 +160,7 @@ "upstream_dns_configured_in_file": "Diatur dalam {{path}}", "test_upstream_btn": "Uji hulu", "upstreams": "Upstream", + "upstream": "Server upstream", "apply_btn": "Terapkan", "disabled_filtering_toast": "Penyaringan nonaktif", "enabled_filtering_toast": "Penyaringan aktif", @@ -556,7 +559,7 @@ "rewrite_A": "<0>A: nilai khusus, biarkan <0>A merekam dari upstream", "rewrite_AAAA": "<0>AAAA: nilai khusus, biarkan <0>AAAA merekam dari upstream", "disable_ipv6": "Nonaktifkan penyelesaian alamat IPv6", - "disable_ipv6_desc": "Jatuhkan semua kueri DNS untuk alamat IPv6 (ketik AAAA).", + "disable_ipv6_desc": "Hapus semua kueri DNS untuk alamat IPv6 (ketik AAAA) dan hapus petunjuk IPv6 dari respons HTTPS.", "fastest_addr": "Alamat IP tercepat", "fastest_addr_desc": "Kuiri semua server DNS dan kembalikan alamat IP tercepat diantara semua tanggapan. Ini memperlambat pencarian DNS Sebagai Rumah AdGuard harus menunggu tanggapan dari semua server DNS, tapi meningkatkan konektivitas keseluruhan.", "autofix_warning_text": "Apabila anda menekan \"Perbaiki\", AdGuardHome akan mengatur sistem anda untuk menggunakan server DNS AdGuardHome.", @@ -644,5 +647,27 @@ "confirm_dns_cache_clear": "Apakah Anda yakin ingin menghapus cache DNS?", "cache_cleared": "Cache DNS berhasil dibersihkan", "clear_cache": "Hapus cache", - "protection_section_label": "Perlindungan" + "theme_light_desc": "Tema terang", + "protection_section_label": "Perlindungan", + "schedule_services": "Menjeda pemblokiran layanan", + "schedule_services_desc": "Mengonfigurasi jadwal jeda filter pemblokiran layanan", + "schedule_services_desc_client": "Mengonfigurasi jadwal jeda filter pemblokiran layanan untuk klien ini", + "schedule_desc": "Tetapkan periode tidak aktif untuk layanan yang diblokir", + "schedule_invalid_select": "Waktu mulai harus sebelum waktu akhir", + "schedule_select_days": "Pilih hari", + "schedule_timezone": "Pilih zona waktu", + "schedule_current_timezone": "Zona waktu saat ini: {{value}}", + "schedule_time_all_day": "Sepanjang hari", + "schedule_modal_description": "Jadwal ini akan menggantikan jadwal sekarang untuk hari yang sama. Setiap hari di setiap minggu hanya boleh ada satu periode tidak aktif.", + "schedule_modal_time_off": "Tidak ada pemblokiran layanan:", + "schedule_new": "Jadwal baru", + "schedule_edit": "Edit jadwal", + "schedule_save": "Simpan jadwal", + "sunday_short": "Ming", + "monday_short": "Sen", + "tuesday_short": "Sel", + "wednesday_short": "Rab", + "thursday_short": "Kam", + "friday_short": "Jum", + "saturday_short": "Sab" } diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json index c4ddf3aa..f98bffb3 100644 --- a/client/src/__locales/it.json +++ b/client/src/__locales/it.json @@ -7,12 +7,15 @@ "load_balancing": "Bilanciamento del carico", "load_balancing_desc": "Interroga un server upstream per volta. AdGuard Home utilizzerà un algoritmo casuale ponderato per la selezione del server, in maniera tale da scegliere spesso il più veloce.", "bootstrap_dns": "Server DNS bootstrap", - "bootstrap_dns_desc": "I server DNS di bootstrap sono utilizzati per risolvere gli indirizzi IP dei risolutori DoH/DoT specificati come upstream.", + "bootstrap_dns_desc": "Indirizzi IP dei server DNS utilizzati per risolvere gli indirizzi IP dei resolver DoH/DoT specificati come upstream. I commenti non sono ammessi.", + "fallback_dns_title": "Server DNS di fallback", + "fallback_dns_desc": "Elenco dei server DNS fallback utilizzati quando i server DNS upstream non rispondono. La sintassi è la stessa del campo principale upstream sopra.", + "fallback_dns_placeholder": "Inserisci un server DNS fallback per riga", "local_ptr_title": "Server DNS privati inversi", "local_ptr_desc": "I server DNS che AdGuard Home utilizza per le richieste PTR locali. Questi server vengono utilizzati per risolvere i nomi host dei client con indirizzi IP privati, ad esempio \"192.168.12.34\", utilizzando il DNS inverso. Se non è impostato, AdGuard Home utilizzerà gli indirizzi dei resolutori DNS predefiniti del tuo sistema operativo ad eccezione degli indirizzi di AdGuard Home stesso.", "local_ptr_default_resolver": "Per impostazione predefinita, AdGuard Home utilizzerà i seguenti risolutori DNS inversi: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home non è stato in grado di determinare i risolutori DNS inversi privati adatti per questo sistema.", - "local_ptr_placeholder": "Inserisci un indirizzo server per riga", + "local_ptr_placeholder": "Inserisci un indirizzo IP per riga", "resolve_clients_title": "Attiva la risoluzione inversa degli indirizzi IP dei client", "resolve_clients_desc": "Risolve inversamente gli indirizzi IP dei client nei loro nomi host inviando richieste PTR ai risolutori corrispondenti (server DNS privati per client locali, server upstream per client con indirizzi IP pubblici).", "use_private_ptr_resolvers_title": "Utilizza dei resolver rDNS privati", @@ -125,6 +128,8 @@ "top_clients": "Client più utilizzati", "no_clients_found": "Nessun client trovato", "general_statistics": "Statistiche generali", + "top_upstreams": "Top upstream", + "no_upstreams_data_found": "Nessun dato upstream trovato", "number_of_dns_query_days": "Numero di richieste DNS elaborate negli ultimi {{count}} giorni", "number_of_dns_query_days_plural": "Numero di richieste DNS elaborate negli ultimi {{count}} giorni", "number_of_dns_query_24_hours": "Numero di richieste DNS elaborate nelle ultime 24 ore", @@ -134,6 +139,7 @@ "enforced_save_search": "Ricerca sicura forzata", "number_of_dns_query_to_safe_search": "Numero di richieste DNS dai motori di ricerca per i quali la Ricerca Sicura è stata forzata", "average_processing_time": "Tempo di elaborazione medio", + "processing_time": "Tempo di elaborazione", "average_processing_time_hint": "Tempo medio in millisecondi per elaborare una richiesta DNS", "block_domain_use_filters_and_hosts": "Blocca domini utilizzando filtri e file hosts", "filters_block_toggle_hint": "Puoi impostare le regole di blocco nelle impostazioni dei Filtri.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Configurato su {{path}}", "test_upstream_btn": "Testa gli upstream", "upstreams": "Upstream", + "upstream": "Upstream", "apply_btn": "Applica", "disabled_filtering_toast": "Disattiva filtri", "enabled_filtering_toast": "Attiva filtri", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: valore speciale, mantieni registrazioni <0>A dall'upstream", "rewrite_AAAA": "<0>AAAA: valore speciale, mantieni registrazioni <0>AAAA dall'upstream", "disable_ipv6": "Disattiva risoluzione indirizzi IPv6", - "disable_ipv6_desc": "Elimina tutte le richieste DNS per gli indirizzi IPv6 (tipo AAAA).", + "disable_ipv6_desc": "Eliminare tutte le query DNS per gli indirizzi IPv6 (tipo AAAA) e rimuovere i suggerimenti IPv6 dalle risposte HTTPS.", "fastest_addr": "Indirizzo IP più veloce", "fastest_addr_desc": "Interroga tutti i server DNS e restituisci l'indirizzo IP più veloce tra tutte le risposte. Ciò rallenterà le richieste DNS poiché AdGuard Home dovrà attendere le risposte da tutti i server DNS, ma ciò migliorerà complessivamente la connettività.", "autofix_warning_text": "Se fai clic su \"Correggi\", AdGuardHome configurerà il tuo sistema per utilizzare il server DNS AdGuardHome.", @@ -676,5 +683,37 @@ "protection_section_label": "Protezione", "log_and_stats_section_label": "Registro richieste e statistiche", "ignore_query_log": "Ignora questo client nel registro delle richieste", - "ignore_statistics": "Ignora questo cliente nelle statistiche" + "ignore_statistics": "Ignora questo cliente nelle statistiche", + "schedule_services": "Sospendere il blocco del servizio", + "schedule_services_desc": "Configura la pianificazione della pausa del filtro di blocco dei servizi", + "schedule_services_desc_client": "Configura la pianificazione della pausa del filtro di blocco dei servizi per questo client", + "schedule_desc": "Impostare periodi di inattività per i servizi bloccati", + "schedule_invalid_select": "L'ora di inizio deve essere precedente all'ora di fine", + "schedule_select_days": "Selezionare i giorni", + "schedule_timezone": "Selezionare un fuso orario", + "schedule_current_timezone": "Fuso orario attuale: {{value}}", + "schedule_time_all_day": "Tutto il giorno", + "schedule_modal_description": "Questo pianificazione sostituirà tutti gli orari esistenti per lo stesso giorno della settimana. Ogni giorno della settimana può avere un solo periodo d'inattività.", + "schedule_modal_time_off": "Sospendere il blocco del servizio:", + "schedule_new": "Nuovo programma", + "schedule_edit": "Modificare programma", + "schedule_save": "Salvare programma", + "schedule_add": "Aggiungere programma", + "schedule_remove": "Rimuovere programma", + "schedule_from": "Dal", + "schedule_to": "Al", + "sunday": "Domenica", + "monday": "Lunedi", + "tuesday": "Martedì", + "wednesday": "Mercoledì", + "thursday": "Giovedì", + "friday": "Venerdì", + "saturday": "Sabato", + "sunday_short": "Dom", + "monday_short": "Lun", + "tuesday_short": "Mar", + "wednesday_short": "Mer", + "thursday_short": "Gio", + "friday_short": "Ven", + "saturday_short": "Sab" } diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json index 95199d5a..0f4a9528 100644 --- a/client/src/__locales/ja.json +++ b/client/src/__locales/ja.json @@ -7,16 +7,19 @@ "load_balancing": "ロードバランシング", "load_balancing_desc": "一度に1つのアップストリームサーバに処理要求します。 AdGuard Homeは、重み付きランダムアルゴリズム(weighted random algorithm)を使用してサーバを選択するため、最速のサーバがより頻繁に使用されます。", "bootstrap_dns": "ブートストラップDNSサーバ", - "bootstrap_dns_desc": "ブートストラップDNSサーバは、上流として指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されます。", + "bootstrap_dns_desc": "アップストリームとして指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されるDNSサーバーのIPアドレスです。(コメントは許可されていません)", + "fallback_dns_title": "フォールバックDNSサーバー", + "fallback_dns_desc": "アップストリームDNSサーバーが応答しない場合に使用されるフォールバックDNSサーバーのリストです。構文は上記のmain upstreamsフィールドと同じです。", + "fallback_dns_placeholder": "フォールバックDNSサーバーを1行に1つずつ入力してください。", "local_ptr_title": "プライベートリバースDNSサーバー", "local_ptr_desc": "AdGuard HomeがローカルPTRクエリに使用するDNSサーバーです。これらのサーバーは、rDNSを使ってプライベートIPアドレス(例えば\"192.168.12.34\")を持つクライアントのホスト名を解決するために使用されます。設定されていない場合、AdGuard HomeはOSのデフォルトDNSリゾルバーのアドレス(AdGuard Home自体のアドレスを除く)を自動的に使用します。", "local_ptr_default_resolver": "デフォルトでは、AdGuard Homeは次のリバースDNSリゾルバを使用します: {{ip}}", "local_ptr_no_default_resolver": "AdGuard Homeは、このシステムに適したプライベートリバースDNSリゾルバを特定できませんでした。", - "local_ptr_placeholder": "1行に1つのサーバを入力してください。", + "local_ptr_placeholder": "IPアドレスを1行に1つずづ入力してください。", "resolve_clients_title": "クライアントのIPアドレスの逆解決を有効にする", - "resolve_clients_desc": "対応するリゾルバー(ローカルクライアントの場合はプライベートDNSサーバ、パブリックIPを持つクライアントの場合は上流サーバ)にPTRクエリを送信することにより、クライアントのIPアドレスをホストネームに逆解決します。", + "resolve_clients_desc": "対応するリゾルバー(ローカルクライアントの場合はプライベートDNSサーバ、パブリックIPを持つクライアントの場合はアップストリームサーバー)にPTRクエリを送信することにより、クライアントのIPアドレスをホストネームに逆解決します。", "use_private_ptr_resolvers_title": "プライベートリバースDNSリゾルバを使用", - "use_private_ptr_resolvers_desc": "これらの上流サーバを使用して、ローカルで提供されるアドレスのリバースDNSルックアップを実行します。無効にすると、AdGuard Homeは、DHCP, /etc/hosts などから認識されるクライアントを除き、すべてのこのようなPTR要求にNXDOMAINで応答します。", + "use_private_ptr_resolvers_desc": "これらのアップストリームサーバーを使用して、ローカルで提供されるアドレスのリバースDNSルックアップを実行します。無効にすると、AdGuard Homeは、DHCP, /etc/hosts などから認識されるクライアントを除き、すべてのこのようなPTR要求にNXDOMAINで応答します。", "check_dhcp_servers": "DHCPサーバをチェックする", "save_config": "構成を保存する", "enabled_dhcp": "DHCPサーバを有効にしました", @@ -125,6 +128,8 @@ "top_clients": "トップクライアント", "no_clients_found": "クライアント情報はありません", "general_statistics": "全般的な統計", + "top_upstreams": "上位のアップストリーム", + "no_upstreams_data_found": "アップストリームのデータが見つかりません", "number_of_dns_query_days": "過去{{count}}日間に処理されたDNSクエリの数", "number_of_dns_query_days_plural": "過去{{count}}日間に処理されたDNSクエリの数", "number_of_dns_query_24_hours": "過去24時間に処理されたDNSクエリの数", @@ -134,6 +139,7 @@ "enforced_save_search": "強制されたセーフサーチ", "number_of_dns_query_to_safe_search": "セーフサーチが強制適用された検索エンジンへのDNSリクエストの数", "average_processing_time": "平均処理時間", + "processing_time": "処理時間", "average_processing_time_hint": "DNSリクエストの処理にかかる平均時間(ミリ秒単位)", "block_domain_use_filters_and_hosts": "フィルタとhostsファイルを使用してドメインをブロックする", "filters_block_toggle_hint": "フィルタの設定でブロックするルールを設定することができます。", @@ -153,11 +159,12 @@ "custom_filtering_rules": "カスタム・フィルタリングルール", "encryption_settings": "暗号化設定", "dhcp_settings": "DHCP設定", - "upstream_dns": "上流DNSサーバ", - "upstream_dns_help": "サーバのアドレスは1行に1つずつ入力してください。上流DNSサーバの構成設定について詳しくはこちらでご確認いただけます。", + "upstream_dns": "アップストリームDNSサーバー", + "upstream_dns_help": "サーバのアドレスは1行に1つずつ入力してください。アップストリームDNSサーバーの構成設定について詳しくはこちらでご確認いただけます。", "upstream_dns_configured_in_file": "{{path}} にて設定されています", - "test_upstream_btn": "上流サーバをテストする", - "upstreams": "上流", + "test_upstream_btn": "アップストリームをテストする", + "upstreams": "アップストリーム", + "upstream": "アップストリーム", "apply_btn": "適用する", "disabled_filtering_toast": "フィルタリングを無効にしました", "enabled_filtering_toast": "フィルタリングを有効にしました", @@ -220,7 +227,7 @@ "example_upstream_tcp_port": "レギュラーDNS(over TCP、ポート付き);", "example_upstream_tcp_hostname": "通常のDNS(over TCP, ホスト名)。", "all_lists_up_to_date_toast": "すべてのリストは既に最新です", - "updated_upstream_dns_toast": "上流DNSサーバを保存しました。", + "updated_upstream_dns_toast": "アップストリームサーバーを保存しました。", "dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています", "dns_test_not_ok_toast": "サーバ \"{{key}}\": 使用できませんでした。正しく入力されているかどうかを確認してください", "dns_test_warning_toast": "アップストリーム\"{{key}}\"はテストリクエストに応答せず、正しく動作しない可能性があります。", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A:特別な値、アップストリームからの<0>A記録を保持します。", "rewrite_AAAA": "<0>AAAA:特別な値、アップストリームからの<0>AAAA記録を保持します。", "disable_ipv6": "IPv6アドレスの解決を無効にする", - "disable_ipv6_desc": "IPv6アドレス(タイプAAAA)に対するすべてのDNSクエリをドロップします。", + "disable_ipv6_desc": "IPv6アドレス(タイプAAAA)に対するDNSクエリをすべて破棄し、HTTPS応答から IPv6 hint を削除します。", "fastest_addr": "最速のIPアドレス", "fastest_addr_desc": "すべてのDNSサーバーに処理要求し、全応答の中で最速のIPアドレスを返します。これにより、AdGuard HomeがすべてのDNSサーバーからの応答を待つ必要があるため、DNSクエリが遅くなりますが、全体的な接続性は向上します。", "autofix_warning_text": "「修正」をクリックすると、AdGuardHomeはAdGuardHome DNSサーバを使用するようにシステムを構成します。", @@ -623,7 +630,7 @@ "enter_cache_size": "キャッシュサイズ(バイト単位)を入力してください", "enter_cache_ttl_min_override": "最小TTL(秒単位)を入力してください", "enter_cache_ttl_max_override": "最大TTL(秒単位)を入力してください", - "cache_ttl_min_override_desc": "DNS応答をキャッシュするとき、上流サーバから受信した短いTTL(秒単位)を延長します。", + "cache_ttl_min_override_desc": "DNS応答をキャッシュするとき、アップストリームサーバーから受信した短いTTL(秒単位)を延長します。", "cache_ttl_max_override_desc": "DNSキャッシュ内のエントリの最大TTL(秒単位)を設定します。", "ttl_cache_validation": "最小キャッシュTTL上書き値は最大値以下にする必要があります", "cache_optimistic": "Optimistic cashing (オプティミスティック・キャッシュ)", @@ -676,5 +683,37 @@ "protection_section_label": "AdGuardによる保護", "log_and_stats_section_label": "クエリ・ログと統計情報", "ignore_query_log": "クエリ・ログでこのクライアントを無視する", - "ignore_statistics": "統計でこのクライアントを無視する" + "ignore_statistics": "統計でこのクライアントを無視する", + "schedule_services": "サービスブロックの一時停止", + "schedule_services_desc": "サービスブロックフィルタの一時停止スケジュールを設定できます。", + "schedule_services_desc_client": "このクライアントに対するサービスブロックフィルタの一時停止スケジュールを設定できます。", + "schedule_desc": "ブロックされたサービスの非アクティブ期間を設定できます。", + "schedule_invalid_select": "開始時間は終了時間より前である必要があります", + "schedule_select_days": "曜日を選択", + "schedule_timezone": "タイムゾーンを選択", + "schedule_current_timezone": "現在のタイムゾーン: {{value}}", + "schedule_time_all_day": "まる一日", + "schedule_modal_description": "※このスケジュールは、同じ曜日に対する既存スケジュールがある場合、すべて置き換えます。\n各曜日ごとに設定できる非アクティブ期間は一つに限ります。", + "schedule_modal_time_off": "サービスブロックなし期間:", + "schedule_new": "新スケジュールの追加", + "schedule_edit": "スケジュールを編集する", + "schedule_save": "スケジュールを保存する", + "schedule_add": "スケジュールを追加する", + "schedule_remove": "スケジュールを削除する", + "schedule_from": "開始時間", + "schedule_to": "終了時間", + "sunday": "日曜日", + "monday": "月曜日", + "tuesday": "火曜日", + "wednesday": "水曜日", + "thursday": "木曜日", + "friday": "金曜日", + "saturday": "土曜日", + "sunday_short": "日", + "monday_short": "月", + "tuesday_short": "火", + "wednesday_short": "水", + "thursday_short": "木", + "friday_short": "金", + "saturday_short": "土" } diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json index 6ec7a352..549e21c0 100644 --- a/client/src/__locales/ko.json +++ b/client/src/__locales/ko.json @@ -7,12 +7,15 @@ "load_balancing": "로드 밸런싱", "load_balancing_desc": "한 번에 하나의 서버씩 질의합니다. AdGuard Home은 가중 랜덤 알고리즘를 사용해서 가장 빠른 서버가 자주 사용되도록 서버를 선택합니다.", "bootstrap_dns": "부트스트랩 DNS 서버", - "bootstrap_dns_desc": "부트스트랩 DNS 서버는 업스트림으로 지정한 DoH/DoT 서버의 IP 주소를 확인하는 데 사용합니다.", + "bootstrap_dns_desc": "업스트림으로 지정한 DoH/DoT 리졸버의 IP 주소를 확인하는 데 사용되는 DNS 서버의 IP 주소입니다. 주석은 허용되지 않습니다.", + "fallback_dns_title": "폴백 DNS 서버", + "fallback_dns_desc": "업스트림 DNS 서버가 응답하지 않을 때 사용되는 폴백 DNS 서버 목록입니다. 구문은 위의 기본 업스트림 필드와 동일합니다.", + "fallback_dns_placeholder": "한 줄에 하나의 폴백 DNS 서버를 입력하세요.", "local_ptr_title": "프라이빗 역방향 DNS 서버", "local_ptr_desc": "AdGuard Home이 로컬 PTR 쿼리에 사용하는 DNS 서버입니다. 이러한 서버는 역방향 DNS를 사용하여 개인 IP 주소(예: '192.168.12.34')가 있는 클라이언트의 호스트 이름을 확인하는 데 사용됩니다. 설정되지 않은 경우, AdGuard Home은 AdGuard Home의 주소를 제외하고 운영 체제의 기본 DNS 리졸버의 주소를 사용합니다.", "local_ptr_default_resolver": "기본적으로 AdGuard Home에서는 {{ip}} 역방향 DNS 서버를 이용합니다.", "local_ptr_no_default_resolver": "AdGuard Home에서 이 시스템에 적합한 사설 역방향 프라이빗 DNS 서버를 결정할 수 없습니다.", - "local_ptr_placeholder": "한 줄에 하나씩 서버 주소 입력", + "local_ptr_placeholder": "한 줄에 하나씩 IP 주소를 입력하세요.", "resolve_clients_title": "클라이언트 IP 주소에 대한 호스트명 확인 활성화", "resolve_clients_desc": "해당 서버에 대한 PTR 쿼리를 통해 클라이언트의 도메인 이름을 정의합니다. (로컬 클라이언트의 경우 프라이빗 DNS 서버, 공용 IP 주소가 있는 클라이언트의 경우 업스트림 서버).", "use_private_ptr_resolvers_title": "프라이빗 역방향 DNS 리졸버 사용", @@ -125,6 +128,8 @@ "top_clients": "클라이언트", "no_clients_found": "클라이언트가 없습니다", "general_statistics": "일반 통계", + "top_upstreams": "상위 업스트림", + "no_upstreams_data_found": "업스트림 데이터 없음", "number_of_dns_query_days": "최근 {{count}}일 동안 처리된 DNS 쿼리의 수", "number_of_dns_query_days_plural": "최근 {{count}}일 동안 처리된 DNS 쿼리의 수", "number_of_dns_query_24_hours": "최근 24시간 동안 처리된 DNS 쿼리의 수", @@ -134,6 +139,7 @@ "enforced_save_search": "세이프서치 강제", "number_of_dns_query_to_safe_search": "세이프서치가 적용된 검색 엔진에 대해 DNS 요청 수", "average_processing_time": "평균처리 시간", + "processing_time": "처리 시간", "average_processing_time_hint": "DNS 요청 처리시 평균 시간(밀리초)", "block_domain_use_filters_and_hosts": "필터 및 호스트 파일을 사용하여 도메인 차단", "filters_block_toggle_hint": "차단규칙필터을 설정할 수 있습니다.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "{{path}}에서 구성됨", "test_upstream_btn": "업스트림 테스트", "upstreams": "업스트림", + "upstream": "업스트림", "apply_btn": "적용", "disabled_filtering_toast": "필터링 비활성화됨", "enabled_filtering_toast": "필터링 활성화됨", @@ -564,7 +571,7 @@ "rewrite_A": "<0> A: 특수 값, 업스트림에서 <0> A 기록 유지", "rewrite_AAAA": "<0> AAAA: 특수 값, 업스트림에서 <0> AAAA 기록 유지", "disable_ipv6": "IPv6 주소 확인 비활성화", - "disable_ipv6_desc": "IPv6 주소(타입 AAAA)의 모든 DNS 쿼리가 무시됩니다.", + "disable_ipv6_desc": "IPv6 주소(AAAA 유형)에 대한 모든 DNS 쿼리를 무시하고 HTTPS 유형 응답에서 IPv6 데이터를 제거합니다.", "fastest_addr": "가장 빠른 IP 주소", "fastest_addr_desc": "모든 DNS 서버에 쿼리를 수행한 다음 반응이 가장 빠른 IP주소를 반송합니다. AdGuard Home이 모든 DNS 서버의 응답을 기다려야 하기 때문에 DNS 쿼리 속도가 느려지지만 전반적인 연결이 향상됩니다.", "autofix_warning_text": "'수정'을 클릭하면 AdGuard Home이 AdGuard Home DNS 서버를 사용하도록 시스템을 설정합니다.", @@ -676,5 +683,37 @@ "protection_section_label": "보호", "log_and_stats_section_label": "쿼리 로그 및 통계", "ignore_query_log": "쿼리 로그에서 이 클라이언트 무시", - "ignore_statistics": "통계에서 이 클라이언트 무시" + "ignore_statistics": "통계에서 이 클라이언트 무시", + "schedule_services": "서비스 차단 일시 중지", + "schedule_services_desc": "서비스 차단 필터의 일시 중지 일정을 구성하세요.", + "schedule_services_desc_client": "이 클라이언트에 대한 서비스 차단 필터의 일시 중지 일정을 구성하세요.", + "schedule_desc": "차단된 서비스에 대한 비활성 기간을 설정하세요.", + "schedule_invalid_select": "시작 시간은 종료 시간 이전이어야 합니다.", + "schedule_select_days": "요일 선택", + "schedule_timezone": "표준 시간대 선택", + "schedule_current_timezone": "현재 시간대: {{value}}", + "schedule_time_all_day": "하루 종일", + "schedule_modal_description": "이 일정은 같은 요일의 기존 일정을 대체합니다. 각 요일은 단 한 번의 비활성 기간만 가질 수 있습니다.", + "schedule_modal_time_off": "서비스 차단이 비활성화된 요일 및 시간", + "schedule_new": "새로운 일정", + "schedule_edit": "일정 수정", + "schedule_save": "일정 저장", + "schedule_add": "일정 추가", + "schedule_remove": "일정 제거", + "schedule_from": "시작 시간", + "schedule_to": "종료 시간", + "sunday": "일요일", + "monday": "월요일", + "tuesday": "화요일", + "wednesday": "수요일", + "thursday": "목요일", + "friday": "금요일", + "saturday": "토요일", + "sunday_short": "일", + "monday_short": "월", + "tuesday_short": "화", + "wednesday_short": "수", + "thursday_short": "목", + "friday_short": "금", + "saturday_short": "토" } diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json index 6bde6d51..a19fa3c2 100644 --- a/client/src/__locales/nl.json +++ b/client/src/__locales/nl.json @@ -7,12 +7,15 @@ "load_balancing": "Volume balanceren", "load_balancing_desc": "Eén server per keer bevragen. AdGuard Home gebruikt hiervoor een gewogen willekeurig algoritme om de server te kiezen zodat de snelste server meer zal gebruikt worden.", "bootstrap_dns": "Bootstrap DNS-servers", - "bootstrap_dns_desc": "Bootstrap DNS-servers worden gebruikt om IP-adressen op te lossen van de DoH / DoT-resolvers die u opgeeft als upstreams.", + "bootstrap_dns_desc": "IP-adressen van DNS-servers die worden gebruikt om IP-adressen om te zetten van de DoH/DoT-resolvers die je opgeeft als upstreams. Opmerkingen zijn niet toegestaan.", + "fallback_dns_title": "Back-up DNS-servers", + "fallback_dns_desc": "Lijst met DNS-back-up-noodservers die worden gebruikt wanneer upstream DNS-servers niet reageren. De syntaxis is hetzelfde als in het veld hoofdstroomopwaarts hierboven.", + "fallback_dns_placeholder": "Voer één DNS-back-upserver per regel in", "local_ptr_title": "Private omgekeerde DNS-servers", "local_ptr_desc": "De DNS-servers die AdGuard Home gebruikt voor lokale PTR-zoekopdrachten. Deze servers worden gebruikt om PTR-verzoeken voor adressen in privé-IP-bereiken op te lossen, bijvoorbeeld \"192.168.12.34\", met behulp van reverse DNS. Indien niet ingesteld, gebruikt AdGuard Home de adressen van de standaard DNS-resolvers van uw besturingssysteem, behalve de adressen van AdGuard Home zelf.", "local_ptr_default_resolver": "Standaard gebruikt AdGuard Home de volgende omgekeerde DNS-resolvers: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home kon voor dit systeem geen geschikte private omgekeerde DNS-resolvers bepalen.", - "local_ptr_placeholder": "Voer één serveradres per regel in", + "local_ptr_placeholder": "Voer één IP-adres per regel in", "resolve_clients_title": "Omzetten van hostnamen van clients inschakelen", "resolve_clients_desc": "Indien ingeschakeld, zal AdGuard Home proberen om IP-adressen van apparaten te converteren in hun hostnamen door PTR-verzoeken te sturen naar overeenkomstige resolvers (privé-DNS-servers voor lokale apparaten, upstream-server voor apparaten met een openbaar IP-adres).", "use_private_ptr_resolvers_title": "Private omgekeerde DNS-resolvers gebruiken", @@ -125,6 +128,8 @@ "top_clients": "Top gebruikers", "no_clients_found": "Geen gebruikers gevonden", "general_statistics": "Algemene statistieken", + "top_upstreams": "Top upstreams", + "no_upstreams_data_found": "Geen upstreams-gegevens gevonden", "number_of_dns_query_days": "Aantal verwerkte DNS aanvragen van de laatste {{count}} dag", "number_of_dns_query_days_plural": "Aantal verwerkte DNS aanvragen van de laatste {{count}} dagen", "number_of_dns_query_24_hours": "Aantal verwerkte DNS aanvragen van de laatste 24 uur", @@ -134,6 +139,7 @@ "enforced_save_search": "Geforceerd veilig zoeken", "number_of_dns_query_to_safe_search": "Aantal DNS aanvragen in zoekmachines dmv geforceerd veilig zoeken", "average_processing_time": "Gemiddelde procestijd", + "processing_time": "Verwerkingstijd", "average_processing_time_hint": "Gemiddelde verwerkingstijd in milliseconden van een DNS aanvraag", "block_domain_use_filters_and_hosts": "Domeinen blokkeren d.m.v. filters en host-bestanden", "filters_block_toggle_hint": "Je kan blokkeringsregels toevoegen in de Filters instellingen.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Geconfigureerd in {{path}}", "test_upstream_btn": "Test upstream", "upstreams": "Upstreams", + "upstream": "Upstream", "apply_btn": "Toepassen", "disabled_filtering_toast": "Filters uitgeschakeld", "enabled_filtering_toast": "Filters ingeschakeld", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: speciale waarde, <0>A records uit de upstream bewaren", "rewrite_AAAA": "<0>AAAA: speciale waarde, <0>AAAA records uit de upstream bewaren", "disable_ipv6": "Oplossen IPv6-adressen uitschakelen", - "disable_ipv6_desc": "Alle DNS-query's voor IPv6-adressen (type AAAA) verwijderen.", + "disable_ipv6_desc": "Alle DNS-query's voor IPv6-adressen (type AAAA) verwijderen en IPv6-hints uit HTTPS-antwoorden verwijderen.", "fastest_addr": "Snelste IP adres", "fastest_addr_desc": "Alle DNS-servers bevragen en het snelste IP adres terugkoppelen. Dit zal de DNS verzoeken vertragen omdat AdGuard Home moet wachten op de antwoorden van alles DNS-servers, maar verbetert wel de connectiviteit.", "autofix_warning_text": "Als je op \"Repareren\" klikt, configureert AdGuard Home jouw systeem om de AdGuard Home DNS-server te gebruiken.", @@ -676,5 +683,37 @@ "protection_section_label": "Bescherming", "log_and_stats_section_label": "Aanvragenlogboek en statistieken", "ignore_query_log": "Deze client negeren in het aanvragenlogboek", - "ignore_statistics": "Deze client negeren in de statistieken" + "ignore_statistics": "Deze client negeren in de statistieken", + "schedule_services": "Serviceblokkering onderbreken", + "schedule_services_desc": "Het pauzeschema van het serviceblokkeringsfilter configureren", + "schedule_services_desc_client": "Het pauzeschema van het serviceblokkeringsfilter voor deze client configureren", + "schedule_desc": "Inactiviteitsperioden instellen voor geblokkeerde services", + "schedule_invalid_select": "Begintijd moet vóór eindtijd liggen", + "schedule_select_days": "Selecteer dagen", + "schedule_timezone": "Selecteer een tijdzone", + "schedule_current_timezone": "Huidige tijdzone: {{value}}", + "schedule_time_all_day": "De hele dag", + "schedule_modal_description": "Dit schema vervangt alle bestaande schema's voor dezelfde dag van de week. Elke dag van de week kan slechts één inactiviteitsperiode hebben.", + "schedule_modal_time_off": "Geen serviceblokkering:", + "schedule_new": "Nieuw schema", + "schedule_edit": "Schema bewerken", + "schedule_save": "Schema opslaan", + "schedule_add": "Schema toevoegen", + "schedule_remove": "Schema verwijderen", + "schedule_from": "Van", + "schedule_to": "tot", + "sunday": "zondag", + "monday": "maandag", + "tuesday": "dinsdag", + "wednesday": "woensdag", + "thursday": "donderdag", + "friday": "vrijdag", + "saturday": "zaterdag", + "sunday_short": "zo", + "monday_short": "ma", + "tuesday_short": "di", + "wednesday_short": "wo", + "thursday_short": "do", + "friday_short": "vr", + "saturday_short": "za" } diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json index 9607f208..02618184 100644 --- a/client/src/__locales/no.json +++ b/client/src/__locales/no.json @@ -7,7 +7,10 @@ "load_balancing": "Pågangstrykk-utjevning", "load_balancing_desc": "Forespør én tjener om gangen. AdGuard Home vil bruke en 'vektlagt tilfeldig valg'-algoritme for å velge tjener, slik at den raskeste tjeneren blir brukt oftere.", "bootstrap_dns": "Bootstrap-DNS-tjenere", - "bootstrap_dns_desc": "Bootstrap-DNS-tjenere brukes til å oppklare IP-adressene til DoH/DoT-oppklarerene som du har valgt som oppstrømstjenere.", + "bootstrap_dns_desc": "IP-adresser til DNS-servere som brukes til å løse IP-adresser til DoH/DoT-løsere du spesifiserer som oppstrøms. Kommentarer er ikke tillatt.", + "fallback_dns_title": "Reserve DNS-servere", + "fallback_dns_desc": "Liste over reserve-DNS-servere som brukes når oppstrøms DNS-servere ikke svarer. Syntaksen er den samme som i hovedoppstrømsfeltet ovenfor.", + "fallback_dns_placeholder": "Angi én reserve-DNS-server per linje", "local_ptr_title": "Private DNS-tjenere", "local_ptr_desc": "DNS-tjenerne som AdGuard Home bruker for lokale PTR-spørringer. Disse tjenerne brukes til å løse vertsnavnene til klienter med private IP-adresser, for eksempel \"192.168.12.34\", ved bruk av omvendt DNS. Hvis det ikke er angitt, bruker AdGuard Home adressene til standard-DNS-løserne til operativsystemet ditt, bortsett fra adressene til selve AdGuard Home.", "local_ptr_default_resolver": "Som standard, bruker AdGuard Home følgende revers-DNS-oppletere: {{ip}}.", @@ -114,6 +117,8 @@ "top_clients": "Vanligste klienter", "no_clients_found": "Ingen klienter ble funnet", "general_statistics": "Generelle statistikker", + "top_upstreams": "Topp oppstrøms servere", + "no_upstreams_data_found": "Ingen oppstrøms servere data funnet", "number_of_dns_query_days": "Antall DNS-spørringer behandlet for de siste {{count}} dagene", "number_of_dns_query_days_plural": "Antall DNS-forespørsler som ble behandlet de siste {{count}} dagene", "number_of_dns_query_24_hours": "Antall DNS-forespørsler som ble behandlet de siste 24 timene", @@ -123,6 +128,7 @@ "enforced_save_search": "Påtvungede barnevennlige søk", "number_of_dns_query_to_safe_search": "Antall DNS-forespørsler til søkemotorer der \"Safe Search\" ble fremtvunget", "average_processing_time": "Gjennomsnittlig behandlingstid", + "processing_time": "Behandlingstid", "average_processing_time_hint": "Gjennomsnittstid for behandling av DNS-forespørsler i millisekunder", "block_domain_use_filters_and_hosts": "Blokker domener ved hjelp av filtre, «hosts»-filer, og rå domener", "filters_block_toggle_hint": "Du kan sette opp blokkeringsoppføringer i Filtre-innstillingene.", @@ -147,6 +153,7 @@ "upstream_dns_configured_in_file": "Satt opp i {{path}}", "test_upstream_btn": "Test oppstrømstilkoblinger", "upstreams": "Oppstrømstjenere", + "upstream": "Oppstrøms server", "apply_btn": "Benytt", "disabled_filtering_toast": "Skrudde av filtrering", "enabled_filtering_toast": "Skrudde på filtrering", @@ -239,7 +246,7 @@ "query_log_cleared": "Forespørselsloggen ble vellykket slettet", "query_log_updated": "Forespørselsloggen ble vellykket oppdatert", "query_log_clear": "Tøm forespørselsloggene", - "query_log_retention": "Beholding av forespørselsloggføringene", + "query_log_retention": "Rotasjon av forespørselsloggføringene", "query_log_enable": "Skru på loggføring", "query_log_configuration": "Loggføringskonfigurasjon", "query_log_disabled": "Forespørselsloggen er skrudd av og kan bli satt opp i <0>innstillingene", @@ -423,7 +430,7 @@ "client_confirm_delete": "Er du sikker på at du vil slette klienten «{{key}}»?", "list_confirm_delete": "Er du sikker på at du vil slette denne listen?", "auto_clients_title": "Klienter (kjørende)", - "auto_clients_desc": "Loggføring over klientene som bruker AdGuard Home, men som ikke har blitt lagret i oppsettet", + "auto_clients_desc": "Informasjon om IP-adresser til enheter som bruker eller kan bruke AdGuard Home. Denne informasjonen er samlet inn fra flere kilder, inkludert vertsfiler, omvendt DNS, etc.", "access_title": "Tilgangsinnstillinger", "access_desc": "Her kan du sette opp tilgangsregler for AdGuard Home-DNS-tjeneren.", "access_allowed_title": "Tillatte klienter", @@ -493,6 +500,7 @@ "interval_days": "{{count}} dag", "interval_days_plural": "{{count}} dager", "domain": "Domene", + "ecs": "ECS", "punycode": "Punycode", "answer": "Svar", "filter_added_successfully": "Filteret har blitt vellykket lagt til", @@ -538,7 +546,7 @@ "rewrite_A": "<0>A: spesialverdi, behold <0>A-statutter fra oppstrømstjeneren", "rewrite_AAAA": "<0>AAAA: spesialverdi, behold <0>AAAA-statutter fra oppstrømstjeneren", "disable_ipv6": "Skru av IPv6", - "disable_ipv6_desc": "Hvis dette er skrudd på, vil alle DNS-forespørslene til IPv6-adresser (AAAA-type) bli droppet.", + "disable_ipv6_desc": "Slipp alle DNS-spørringer for IPv6-adresser (type AAAA) og fjern IPv6-hint fra HTTPS-svar.", "fastest_addr": "Raskeste IP-adresse", "fastest_addr_desc": "Forespør alle DNS-tjenerne og hent den raskeste IP-adressen blant alle svarene. Dette vil gjøre DNS-forespørslene tregere, siden vi må vente på svar fra alle DNS-tjenerne, men det vil forbedre tilkoblingen generelt.", "autofix_warning_text": "Hvis du klikker på «Fiks», vil AdGuard Home sette opp systemet ditt til å bruke 'AdGuard Home'-DNS-tjeneren.", @@ -619,5 +627,44 @@ "parental_control": "Foreldrekontroll", "safe_browsing": "Sikker surfing", "served_from_cache": "{{value}} (formidlet fra mellomlageret)", - "protection_section_label": "Beskyttelse" + "theme_dark_desc": "Mørkt tema", + "theme_light_desc": "Lyst tema", + "disable_notify_until_tomorrow": "Deaktiver beskyttelsen til i morgen", + "enable_protection_timer": "Beskyttelse vil være aktivert i {{time}}", + "custom_retention_input": "Angi oppbevaring i timer", + "custom_rotation_input": "Angi rotasjon i timer", + "protection_section_label": "Beskyttelse", + "ignore_statistics": "Ignorer denne klienten i statistikken", + "schedule_services": "Sett blokkering av tjenesten på pause", + "schedule_services_desc": "Konfigurer pauseplanen for tjenesteblokkeringsfilteret", + "schedule_services_desc_client": "Konfigurer pauseplanen for tjenesteblokkeringsfilteret for denne klienten", + "schedule_desc": "Angi inaktivitetsperioder for blokkerte tjenester", + "schedule_invalid_select": "Starttid må være før sluttid", + "schedule_select_days": "Velg dager", + "schedule_timezone": "Velg en tidssone", + "schedule_current_timezone": "Gjeldende tidssone: {{value}}", + "schedule_time_all_day": "Hele dagen", + "schedule_modal_description": "Denne tidsplanen vil erstatte alle eksisterende tidsplaner for samme ukedag. Hver dag i uken kan bare ha én inaktivitetsperiode.", + "schedule_modal_time_off": "Ingen tjenesteblokkering:", + "schedule_new": "Ny tidsplan", + "schedule_edit": "Endre tidsplan", + "schedule_save": "Lagre tidsplan", + "schedule_add": "Legg til tidsplan", + "schedule_remove": "Fjern tidsplanen", + "schedule_from": "Fra", + "schedule_to": "Til", + "sunday": "Søndag", + "monday": "Mandag", + "tuesday": "Tirsdag", + "wednesday": "Onsdag", + "thursday": "Torsdag", + "friday": "Fredag", + "saturday": "Lørdag", + "sunday_short": "søn", + "monday_short": "man", + "tuesday_short": "tir", + "wednesday_short": "ons", + "thursday_short": "tor", + "friday_short": "fre", + "saturday_short": "lør" } diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json index f9e83813..3fdae444 100644 --- a/client/src/__locales/pl.json +++ b/client/src/__locales/pl.json @@ -7,12 +7,15 @@ "load_balancing": "Równoważenie obciążenia", "load_balancing_desc": "Wysyłaj zapytania do jednego serwera nadrzędnego na raz. AdGuard Home używa swojego losowego algorytmu ważonego, aby wybrać serwer, tak aby najszybszy serwer był używany częściej.", "bootstrap_dns": "Serwery DNS Bootstrap", - "bootstrap_dns_desc": "Serwery DNS Bootstrap są używane do ustalenia adresu IP serwerów DoH/DoT, które oznaczysz jako główne serwery DNS.", + "bootstrap_dns_desc": "Adresy IP serwerów DNS używanych do rozpoznawania adresów IP programów rozpoznawania nazw DoH/DoT określonych jako nadrzędne. Komentarze są niedozwolone.", + "fallback_dns_title": "Rezerwowe serwery DNS", + "fallback_dns_desc": "Lista rezerwowych serwerów DNS używanych, gdy nadrzędne serwery DNS nie odpowiadają. Składnia jest taka sama jak w głównym polu powyżej.", + "fallback_dns_placeholder": "Wprowadź jeden rezerwowy serwer DNS w każdym wierszu", "local_ptr_title": "Prywatne odwrotne serwery DNS", "local_ptr_desc": "Serwery DNS, których AdGuard Home używa do lokalnych zapytań PTR. Serwery te są używane do rozpoznawania nazw hostów klientów z prywatnymi adresami IP, na przykład „192.168.12.34”, przy użyciu odwrotnego DNS. Jeśli nie jest ustawiona, AdGuard Home używa adresów domyślnych resolwerów DNS systemu operacyjnego, z wyjątkiem adresów samego AdGuard Home.", "local_ptr_default_resolver": "Domyślnie AdGuard Home używa następujących odwrotnych resolwerów DNS: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home nie mógł określić odpowiednich prywatnych resolwerów DNS dla tego systemu.", - "local_ptr_placeholder": "Wprowadź po jednym adresie serwera w każdym wierszu", + "local_ptr_placeholder": "Wprowadź po jednym adresie IP w każdym wierszu", "resolve_clients_title": "Włącz odwrotne rozpoznawanie adresów IP klientów", "resolve_clients_desc": "Odwróć adresy IP klientów na ich nazwy hostów, wysyłając zapytania PTR do odpowiednich programów tłumaczących (prywatne serwery DNS dla klientów lokalnych, serwery nadrzędne dla klientów z publicznymi adresami IP).", "use_private_ptr_resolvers_title": "Użyj prywatnych odwrotnych resolwerów DNS", @@ -125,6 +128,8 @@ "top_clients": "Główni klienci", "no_clients_found": "Nie znaleziono klienta", "general_statistics": "Ogólne statystyki", + "top_upstreams": "Często żądane serwery nadrzędne", + "no_upstreams_data_found": "Brak danych dotyczących serwerów nadrzędnych", "number_of_dns_query_days": "Liczba przetworzonych zapytań DNS w ciągu ostatnich {{count}} dni", "number_of_dns_query_days_plural": "Liczba przetworzonych zapytań DNS w ciągu ostatnich {{count}} dni", "number_of_dns_query_24_hours": "Liczba zapytań DNS przetworzonych w ciągu ostatnich 24 godzin", @@ -134,6 +139,7 @@ "enforced_save_search": "Wymuszone bezpieczne wyszukiwanie", "number_of_dns_query_to_safe_search": "Liczba żądań DNS kierowanych do wyszukiwarek, dla których wymuszono Bezpieczne wyszukiwanie", "average_processing_time": "Średni czas przetwarzania", + "processing_time": "Czas przetwarzania", "average_processing_time_hint": "Średni czas przetwarzania żądania DNS liczony w milisekundach", "block_domain_use_filters_and_hosts": "Zablokuj domeny za pomocą filtrów i plików host", "filters_block_toggle_hint": "Możesz skonfigurować reguły blokowania w ustawieniach Filtry.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Skonfigurowano w {{path}}", "test_upstream_btn": "Test głównych serwerów DNS", "upstreams": "Główne serwery DNS", + "upstream": "Serwer nadrzędny", "apply_btn": "Zastosuj", "disabled_filtering_toast": "Wyłączone filtrowanie", "enabled_filtering_toast": "Włączone filtrowanie", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: wartość specjalna, zachowaj rekord <0>A z głównego serwera DNS", "rewrite_AAAA": "<0>AAAA: wartość specjalna, zachowaj rekord <0>AAAA z głównego serwera DNS", "disable_ipv6": "Wyłącz rozwiązywanie adresów IPv6", - "disable_ipv6_desc": "Usuń wszystkie zapytania DNS dla adresów IPv6 (typ AAAA).", + "disable_ipv6_desc": "Ignorować wszystkie zapytania DNS dotyczące adresów IPv6 (typ AAAA) i usuwać dane IPv6 z odpowiedzi HTTPS.", "fastest_addr": "Najszybszy adres IP", "fastest_addr_desc": "Zapytaj wszystkie serwery DNS i zwróć najszybszy adres IP spośród wszystkich odpowiedzi. Spowalnia to zapytania DNS, ponieważ AdGuard Home musi czekać na odpowiedzi ze wszystkich serwerów DNS, ale poprawia ogólną łączność.", "autofix_warning_text": "Jeśli klikniesz „Napraw”, AdGuardHome skonfiguruje system do korzystania z serwera DNS AdGuardHome.", @@ -676,5 +683,37 @@ "protection_section_label": "Ochrona", "log_and_stats_section_label": "Dziennik zapytań i statystyki", "ignore_query_log": "Zignoruj tego klienta w dzienniku zapytań", - "ignore_statistics": "Ignoruj tego klienta w statystykach" + "ignore_statistics": "Ignoruj tego klienta w statystykach", + "schedule_services": "Wstrzymanie blokowania serwisów", + "schedule_services_desc": "Ustawianie harmonogramu wstrzymywania filtru blokowania serwisów", + "schedule_services_desc_client": "Ustawianie harmonogramu wstrzymywania filtru blokowania serwisów dla tego klienta", + "schedule_desc": "Ustawianie okresów bezczynności dla zablokowanych serwisów", + "schedule_invalid_select": "Czas rozpoczęcia musi być przed czasem zakończenia", + "schedule_select_days": "Wybierz dni", + "schedule_timezone": "Wybierz strefę czasową", + "schedule_current_timezone": "Aktualna strefa czasowa: {{value}}", + "schedule_time_all_day": "Cały dzień", + "schedule_modal_description": "Ten harmonogram zastąpi wszystkie istniejące harmonogramy na ten sam dzień tygodnia. Każdy dzień tygodnia może mieć tylko jeden okres bezczynności.", + "schedule_modal_time_off": "Blokowanie serwisu jest wyłączone:", + "schedule_new": "Nowy harmonogram", + "schedule_edit": "Edytuj harmonogram", + "schedule_save": "Zapisz harmonogram", + "schedule_add": "Dodaj harmonogram", + "schedule_remove": "Usuń harmonogram", + "schedule_from": "Od", + "schedule_to": "Do", + "sunday": "Niedziela", + "monday": "Poniedziałek", + "tuesday": "Wtorek", + "wednesday": "Środa", + "thursday": "Czwartek", + "friday": "Piątek", + "saturday": "Sobota", + "sunday_short": "Ndz", + "monday_short": "Pon", + "tuesday_short": "Wt", + "wednesday_short": "Śro", + "thursday_short": "Czw", + "friday_short": "Pt", + "saturday_short": "Sob" } diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json index 21094be5..ba951312 100644 --- a/client/src/__locales/pt-br.json +++ b/client/src/__locales/pt-br.json @@ -2,17 +2,20 @@ "client_settings": "Configurações do cliente", "example_upstream_reserved": "um DNS primário <0>para o domínios especificos;", "example_upstream_comment": "um comentário.", - "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos oss servidores DNS primário", + "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS primário", "parallel_requests": "Solicitações paralelas", "load_balancing": "Balanceamento de carga", "load_balancing_desc": "Consulte um servidor DNS primário por vez. O AdGuard Home usa seu algoritmo aleatório ponderado para escolher o servidor para que o servidor mais rápido seja usado com mais frequência.", "bootstrap_dns": "Servidores DNS de inicialização", - "bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams.", + "bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.", + "fallback_dns_title": "Servidores DNS Fallback", + "fallback_dns_desc": "Lista de servidores DNS Fallback usados quando os servidores DNS primários não estão respondendo. A sintaxe é a mesma dos campos de servidores principais na seção acima.", + "fallback_dns_placeholder": "Insira um servidor DNS fallback por linha", "local_ptr_title": "Servidores DNS reversos privados", "local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver solicitações de PTR para endereços em intervalos de IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não estiver definido, o AdGuard Home usa os endereços dos resolvedores de DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.", "local_ptr_default_resolver": "Por padrão, o AdGuard Home usa os seguintes resolvedores de DNS reverso: {{ip}}.", "local_ptr_no_default_resolver": "A página inicial do AdGuard não conseguiu determinar resolvedores DNS reversos privados adequados para este sistema.", - "local_ptr_placeholder": "Insira um endereço de servidor por linha", + "local_ptr_placeholder": "Insira um endereço IP por linha", "resolve_clients_title": "Ativar resolução reversa de endereços IP de clientes", "resolve_clients_desc": "Resolva reversamente os endereços IP dos clientes em seus nomes de host, enviando consultas PTR aos resolvedores correspondentes (servidores DNS privados para clientes locais, servidores upstream para clientes com endereços IP públicos).", "use_private_ptr_resolvers_title": "Usar resolvedores DNS reversos privados", @@ -125,6 +128,8 @@ "top_clients": "Principais clientes", "no_clients_found": "Nenhuma cliente encontrado", "general_statistics": "Estatísticas gerais", + "top_upstreams": "Melhores servidores DNS primários", + "no_upstreams_data_found": "Nenhum dado de servidor DNS primário encontrado", "number_of_dns_query_days": "O número de consultas DNS processadas nos últimos {{count}} dias", "number_of_dns_query_days_plural": "Número de consultas DNS processadas nos últimos {{count}} dias", "number_of_dns_query_24_hours": "O número de consultas DNS processadas nas últimas 24 horas", @@ -134,6 +139,7 @@ "enforced_save_search": "Forçar pesquisa segura", "number_of_dns_query_to_safe_search": "O número de solicitações de DNS para mecanismos de pesquisa para os quais a pesquisa segura foi aplicada", "average_processing_time": "Tempo médio de processamento", + "processing_time": "Tempo de processamento", "average_processing_time_hint": "Tempo médio em milissegundos no processamento de uma solicitação DNS", "block_domain_use_filters_and_hosts": "Bloquear domínios usando arquivos de filtros e hosts", "filters_block_toggle_hint": "Você pode configurar as regras de bloqueio nas configurações de Filtros.", @@ -154,10 +160,11 @@ "encryption_settings": "Configurações de criptografia", "dhcp_settings": "Configurações de DHCP", "upstream_dns": "Servidores DNS primário", - "upstream_dns_help": "Insira um endereço de servidor. um por linha. Saber mais sobre a configuração de servidores DNS primários.", + "upstream_dns_help": "Insira o endereço de servidor, um por linha. Saber mais sobre a configuração de servidores DNS primários.", "upstream_dns_configured_in_file": "Configurado em {{path}}", "test_upstream_btn": "Testar DNS primário", "upstreams": "DNS primário", + "upstream": "Servidor DNS primário", "apply_btn": "Aplicar", "disabled_filtering_toast": "Filtragem desativada", "enabled_filtering_toast": "Filtragem ativada", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: valor especial, mantenha <0>A nos registros do upstream", "rewrite_AAAA": "<0>AAAA: valor especial, mantenha <0>AAAA nos registros do servidor DNS primário", "disable_ipv6": "Desativar resolução de endereços IPv6", - "disable_ipv6_desc": "Descarta todas as consultas DNS para endereços IPv6 (tipo AAAA).", + "disable_ipv6_desc": "Descarta todas as consultas DNS para endereços IPv6 (tipo AAAA) e remove dicas de IPv6 das respostas HTTPS.", "fastest_addr": "Endereço de IP mais rápido", "fastest_addr_desc": "Consulta todos os servidores DNS e retorna o endereço IP mais rápido entre todas as respostas. Isso torna as consultas DNS mais lentas, pois o AdGuard Home tem que esperar pelas respostas de todos os servidores DNS, mas melhora a conectividade geral.", "autofix_warning_text": "Se clicar em \"Corrigir\", o AdGuardHome irá configurar o seu sistema para utilizar o servidor DNS do AdGuardHome.", @@ -676,5 +683,37 @@ "protection_section_label": "Proteção", "log_and_stats_section_label": "Registro de consultas e estatísticas", "ignore_query_log": "Ignorar este cliente no registo de consultas", - "ignore_statistics": "Ignorar este cliente nas estatísticas" + "ignore_statistics": "Ignorar este cliente nas estatísticas", + "schedule_services": "Pausa o bloqueio de serviço", + "schedule_services_desc": "Configura o agendamento de pausa do filtro de bloqueio de serviço", + "schedule_services_desc_client": "Configura o agendamento de pausa do filtro de bloqueio de serviço para este cliente", + "schedule_desc": "Define períodos de inatividade para serviços bloqueados", + "schedule_invalid_select": "O horário de início deve ser antes do horário de término", + "schedule_select_days": "Selecionar dias", + "schedule_timezone": "Selecione um fuso horário", + "schedule_current_timezone": "Fuso horário atual: {{value}}", + "schedule_time_all_day": "O dia todo", + "schedule_modal_description": "Este agendamento substituirá qualquer agendamento existente para o mesmo dia da semana. Cada dia da semana pode ter apenas um período de inatividade.", + "schedule_modal_time_off": "Sem bloqueio de serviço:", + "schedule_new": "Novo agendamento", + "schedule_edit": "Editar agendamento", + "schedule_save": "Salvar agendamento", + "schedule_add": "Adicionar agendamento", + "schedule_remove": "Remover agendamento", + "schedule_from": "De", + "schedule_to": "Para", + "sunday": "Domingo", + "monday": "Segunda-feira", + "tuesday": "Terça-feira", + "wednesday": "Quarta-feira", + "thursday": "Quinta-feira", + "friday": "Sexta-feira", + "saturday": "Sábado", + "sunday_short": "Dom", + "monday_short": "Seg", + "tuesday_short": "Ter", + "wednesday_short": "Quar", + "thursday_short": "Qui", + "friday_short": "Sex", + "saturday_short": "Sab" } diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json index c6e8953a..1d0cf574 100644 --- a/client/src/__locales/pt-pt.json +++ b/client/src/__locales/pt-pt.json @@ -7,12 +7,15 @@ "load_balancing": "Balanceamento de carga", "load_balancing_desc": "Consulte um servidor DNS primário por vez. O AdGuard Home usa seu algoritmo aleatório ponderado para escolher o servidor para que o servidor mais rápido seja usado com mais frequência.", "bootstrap_dns": "Servidores DNS de arranque", - "bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que especifica como upstreams.", + "bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.", + "fallback_dns_title": "Servidores DNS de fallback", + "fallback_dns_desc": "Lista de servidores DNS de fallback usados quando os servidores DNS upstream não estão respondendo. A sintaxe é a mesma do campo principal de upstreams acima.", + "fallback_dns_placeholder": "Insira um servidor DNS de fallback por linha", "local_ptr_title": "Servidores DNS reversos privados", "local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver solicitações de PTR para endereços em intervalos de IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não estiver definido, o AdGuard Home usa os endereços dos resolvedores de DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.", "local_ptr_default_resolver": "Por predefinição, o AdGuard Home usa os seguintes resolvedores de DNS reverso: {{ip}}.", "local_ptr_no_default_resolver": "A página inicial do AdGuard não conseguiu determinar resolvedores DNS reversos privados adequados para este sistema.", - "local_ptr_placeholder": "Insira um endereço de servidor por linha", + "local_ptr_placeholder": "Insira um endereço IP por linha", "resolve_clients_title": "Ativar resolução reversa de endereços IP de clientes", "resolve_clients_desc": "Resolva reversamente os endereços IP dos clientes em seus nomes de host, enviando consultas PTR aos resolvedores correspondentes (servidores DNS privados para clientes locais, servidores upstream para clientes com endereços IP públicos).", "use_private_ptr_resolvers_title": "Usar resolvedores DNS reversos privados", @@ -125,6 +128,8 @@ "top_clients": "Principais clientes", "no_clients_found": "Nenhum cliente foi encontrado", "general_statistics": "Estatísticas gerais", + "top_upstreams": "Melhores servidores DNS primários", + "no_upstreams_data_found": "Nenhum dado de servidor DNS primário encontrado", "number_of_dns_query_days": "Número de consultas DNS processadas durante los últimos {{count}} días", "number_of_dns_query_days_plural": "Número de consultas DNS processadas durante os últimos {{count}} dias", "number_of_dns_query_24_hours": "O número de consultas DNS processadas nas últimas 24 horas", @@ -134,6 +139,7 @@ "enforced_save_search": "Forçar pesquisa segura", "number_of_dns_query_to_safe_search": "O número de solicitações de DNS para motores de busca para os quais a pesquisa segura foi aplicada", "average_processing_time": "Tempo médio de processamento", + "processing_time": "Tempo de processamento", "average_processing_time_hint": "Tempo médio em milissegundos no processamento de uma solicitação DNS", "block_domain_use_filters_and_hosts": "Bloquear domínios usando ficheiros de filtros e hosts", "filters_block_toggle_hint": "Pode configurar as regras de bloqueio nas configurações de Filtros.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Configurado em {{path}}", "test_upstream_btn": "Testar DNS primário", "upstreams": "DNS primário", + "upstream": "Servidor DNS primário", "apply_btn": "Aplicar", "disabled_filtering_toast": "Filtragem desativada", "enabled_filtering_toast": "Filtragem ativada", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: valor especial, mantenha <0>A nos registos do upstream", "rewrite_AAAA": "<0>AAAA: valor especial, mantenha <0>AAAA nos registos do servidor DNS primário", "disable_ipv6": "Desativar resolução de endereços IPv6", - "disable_ipv6_desc": "Descarta todas as consultas DNS para endereços IPv6 (tipo AAAA).", + "disable_ipv6_desc": "Descarte todas as consultas DNS para endereços IPv6 (tipo AAAA) e remova as dicas IPv6 das respostas HTTPS.", "fastest_addr": "Endereço de IP mais rápido", "fastest_addr_desc": "Consulta todos os servidores DNS e retorna o endereço IP mais rápido entre todas as respostas. Isso torna as consultas DNS mais lentas, pois o AdGuard Home tem que esperar pelas respostas de todos os servidores DNS, mas melhora a conectividade geral.", "autofix_warning_text": "Se clicar em \"Corrigir\", o AdGuardHome irá configurar o seu sistema para utilizar o servidor DNS do AdGuardHome.", @@ -676,5 +683,37 @@ "protection_section_label": "Proteção", "log_and_stats_section_label": "Log de consulta e estatísticas", "ignore_query_log": "Ignorar este cliente no log de consulta", - "ignore_statistics": "Ignorar este cliente nas estatísticas" + "ignore_statistics": "Ignorar este cliente nas estatísticas", + "schedule_services": "Pausar bloqueio de serviço", + "schedule_services_desc": "Configure o agendamento de pausa do filtro de bloqueio de serviço", + "schedule_services_desc_client": "Configure o agendamento de pausa do filtro de bloqueio de serviço para este cliente", + "schedule_desc": "Defina períodos de inatividade para serviços bloqueados", + "schedule_invalid_select": "O horário de início deve ser antes do horário de término", + "schedule_select_days": "Selecione os dias", + "schedule_timezone": "Selecione um fuso horário", + "schedule_current_timezone": "Fuso horário atual: {{value}}", + "schedule_time_all_day": "O dia todo", + "schedule_modal_description": "Este horário substituirá quaisquer horários existentes para o mesmo dia da semana. Cada dia da semana só pode ter um período de inatividade.", + "schedule_modal_time_off": "Sem bloqueio de serviço:", + "schedule_new": "Novo agendamento", + "schedule_edit": "Editar agendamento", + "schedule_save": "Salvar agendamento", + "schedule_add": "Adicionar agendamento", + "schedule_remove": "Remover agendamento", + "schedule_from": "De", + "schedule_to": "Para", + "sunday": "Domingo", + "monday": "Segunda-feira", + "tuesday": "Terça-feira", + "wednesday": "Quarta-feira", + "thursday": "Quinta-feira", + "friday": "Sexta-feira", + "saturday": "Sábado", + "sunday_short": "Domingo", + "monday_short": "Seg", + "tuesday_short": "Terça", + "wednesday_short": "Quarta", + "thursday_short": "Quinta", + "friday_short": "Sexta", + "saturday_short": "Sábado" } diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json index 411cf75b..157cf26f 100644 --- a/client/src/__locales/ro.json +++ b/client/src/__locales/ro.json @@ -7,12 +7,15 @@ "load_balancing": "Echilibrare-sarcini", "load_balancing_desc": "Interoghează câte un server în amonte la un moment dat. AdGuard Home utilizează un algoritm de randomizare ponderat pentru a alege serverul, astfel încât cel mai rapid server să fie utilizat mai des.", "bootstrap_dns": "Serverele DNS Bootstrap", - "bootstrap_dns_desc": "Serverele DNS Bootstrap sunt folosite pentru a rezolva adresele IP ale rezolvatorilor DoH/DoT indicați ca upstreams.", + "bootstrap_dns_desc": "Adresele IP ale serverelor DNS utilizate pentru a rezolva adresele IP ale soluțiilor DoH/DoT pe care le specificați ca fiind în amonte. Comentariile nu sunt permise.", + "fallback_dns_title": "Servere DNS de rezervă", + "fallback_dns_desc": "Lista serverelor DNS de rezervă utilizate atunci când serverele DNS din amonte nu răspund. Sintaxa este aceeași ca în câmpul principal din amonte de mai sus.", + "fallback_dns_placeholder": "Introduceți un server DNS de rezervă pe linie", "local_ptr_title": "Servere DNS inverse private", "local_ptr_desc": "Serverele DNS pe care AdGuard Home le utilizează pentru interogările PTR locale. Aceste servere sunt utilizate pentru a rezolva solicitările PTR pentru adrese din intervale IP private, de exemplu „192.168.12.34”, utilizând DNS invers. Dacă nu este configurat, AdGuard Home utilizează adresele rezolvatorilor DNS impliciți ai sistemului dvs. de operare, cu excepția adreselor AdGuard Home în sine.", "local_ptr_default_resolver": "În mod implicit, AdGuard Home utilizează următorii rezolvatori DNS inverși: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home nu a putut determina rezolvatorii DNS privați adecvați pentru acest sistem.", - "local_ptr_placeholder": "Introduceți o adresă de server per linie", + "local_ptr_placeholder": "Introduceți o adresă IP per linie", "resolve_clients_title": "Permiteți rezolvarea inversa a adreselor IP ale clienților", "resolve_clients_desc": "Rezolvă invers adresele IP ale clienților în numele lor de gazde prin trimiterea interogărilor PTR la rezolvatorii corespunzători (servere DNS private pentru clienți locali, servere în amonte pentru clienți cu adrese IP publice).", "use_private_ptr_resolvers_title": "Utilizați rezolvatori DNS inverși privați", @@ -125,6 +128,8 @@ "top_clients": "Clienți de top", "no_clients_found": "Nu au fost găsiți clienți", "general_statistics": "Statistici generale", + "top_upstreams": "Top servere în amonte", + "no_upstreams_data_found": "Nu există date despre serverele din amonte", "number_of_dns_query_days": "Numărul de interogări DNS procesate în ultima {{count}} zi", "number_of_dns_query_days_plural": "Numărul de interogări DNS procesate în ultimele {{count}} zile", "number_of_dns_query_24_hours": "Numărul de interogări DNS procesate în ultimele 24 de ore", @@ -134,6 +139,7 @@ "enforced_save_search": "Căutare protejată întărită", "number_of_dns_query_to_safe_search": "Numărul de interogări DNS pe motoarele de căutare pentru care a fost impusă Căutarea Sigură", "average_processing_time": "Timpul mediu de procesare", + "processing_time": "Timp de procesare", "average_processing_time_hint": "Timp mediu în milisecunde la procesarea unei cereri DNS", "block_domain_use_filters_and_hosts": "Blocați domenii folosind filtre și fișiere hosts", "filters_block_toggle_hint": "Puteți configura regulile de blocare în setările Filtre.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Configurat în {{path}}", "test_upstream_btn": "Testați upstreams", "upstreams": "Upstreams", + "upstream": "Server în amonte", "apply_btn": "Aplică", "disabled_filtering_toast": "Filtrare dezactivată", "enabled_filtering_toast": "Filtrare activată", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: valoare specială, păstrați <0>A înregistrări din amonte", "rewrite_AAAA": "<0>AAAA: valoare specială, păstrați <0>AAAA înregistrări din amonte", "disable_ipv6": "Dezactivați rezolvarea adreselor IPv6", - "disable_ipv6_desc": "Anulați toate interogările DNS pentru adresele IPv6 (tip AAAA).", + "disable_ipv6_desc": "Renunțați la toate interogările DNS pentru adresele IPv6 (tip AAAA) și eliminați indicațiile IPv6 din răspunsurile HTTPS.", "fastest_addr": "Cea mai rapidă adresă IP", "fastest_addr_desc": "Interoghează toate serverele DNS și întoarce adresa IP cea mai rapidă din răspunsuri. Acest lucru încetinește interogările DNS, deoarece AdGuard Home trebuie să aștepte răspunsuri de la toate serverele DNS, dar îmbunătățește conectivitatea generală.", "autofix_warning_text": "Dacă clicați pe \"Fix\", AdGuardHome va configura sistemul dvs. pentru a utiliza serverul DNS AdGuardHome.", @@ -676,5 +683,37 @@ "protection_section_label": "Protecție", "log_and_stats_section_label": "Jurnal de interogări și statistici", "ignore_query_log": "Ignorați acest client în jurnalul de interogări", - "ignore_statistics": "Ignorați acest client în statistici" + "ignore_statistics": "Ignorați acest client în statistici", + "schedule_services": "Întrerupeți blocarea serviciului", + "schedule_services_desc": "Configurați programul de pauză al filtrului de blocare a serviciului", + "schedule_services_desc_client": "Configurați programul de pauză al filtrului de blocare a serviciului pentru acest client", + "schedule_desc": "Setați perioade de inactivitate pentru serviciile blocate", + "schedule_invalid_select": "Ora de început trebuie să fie înaintea orei de sfârșit", + "schedule_select_days": "Selectați zile", + "schedule_timezone": "Selectați un fus orar", + "schedule_current_timezone": "Fus orar curent: {{value}}", + "schedule_time_all_day": "Toată ziua", + "schedule_modal_description": "Acest program va înlocui orice program existent pentru aceeași zi a săptămânii. Fiecare zi a săptămânii poate avea o singură perioadă de inactivitate.", + "schedule_modal_time_off": "Fără blocare a serviciului:", + "schedule_new": "Program nou", + "schedule_edit": "Editare program", + "schedule_save": "Salvați programul", + "schedule_add": "Adăugați program", + "schedule_remove": "Înlăturați programul", + "schedule_from": "De", + "schedule_to": "Până", + "sunday": "Duminică", + "monday": "Luni", + "tuesday": "Marți", + "wednesday": "Miercuri", + "thursday": "Joi", + "friday": "Vineri", + "saturday": "Sâmbătă", + "sunday_short": "du", + "monday_short": "lu", + "tuesday_short": "ma", + "wednesday_short": "mi", + "thursday_short": "jo", + "friday_short": "vi", + "saturday_short": "sa" } diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json index 053cf448..214f770c 100644 --- a/client/src/__locales/ru.json +++ b/client/src/__locales/ru.json @@ -7,7 +7,10 @@ "load_balancing": "Распределение нагрузки\n", "load_balancing_desc": "Запрашивать по одному серверу за раз. AdGuard Home использует алгоритм взвешенного случайного выбора сервера, так что самый быстрый сервер используется чаще.", "bootstrap_dns": "Bootstrap DNS-серверы", - "bootstrap_dns_desc": "Bootstrap DNS-серверы используются для поиска IP-адресов DoH/DoT серверов, которые вы указали.", + "bootstrap_dns_desc": "IP-адреса DNS-серверов, используемых для поиска IP-адресов DoH/DoT upstream-серверов, которые вы указали. Комментарии не допускаются.", + "fallback_dns_title": "Резервные DNS-серверы", + "fallback_dns_desc": "Список резервных DNS-серверов, используемых в тех случаях, когда вышестоящие DNS-серверы недоступны. Синтаксис такой же, как и в поле Upstream DNS-серверы выше.", + "fallback_dns_placeholder": "Введите один резервный DNS-сервер в каждой строке", "local_ptr_title": "Приватные серверы для обратного DNS", "local_ptr_desc": "DNS-серверы, которые AdGuard Home использует для локальных PTR-запросов. Эти серверы используются, чтобы получить доменные имена клиентов с приватными IP-адресами, например «192.168.12.34», с помощью обратного DNS. Если список пуст, AdGuard Home использует DNS-серверы по умолчанию вашей ОС.", "local_ptr_default_resolver": "По умолчанию AdGuard Home использует следующие обратные DNS-резолверы: {{ip}}.", @@ -125,6 +128,8 @@ "top_clients": "Частые клиенты", "no_clients_found": "Клиентов не найдено", "general_statistics": "Общая статистика", + "top_upstreams": "Часто запрашиваемые upstream-серверы", + "no_upstreams_data_found": "Нет данных об upstream-серверах", "number_of_dns_query_days": "Количество DNS-запросов за последний {{count}} день", "number_of_dns_query_days_plural": "Количество DNS запросов, обработанных за последние {{count}} дней", "number_of_dns_query_24_hours": "Количество DNS-запросов за последние 24 часа", @@ -134,6 +139,7 @@ "enforced_save_search": "Применён безопасный поиск", "number_of_dns_query_to_safe_search": "Количество запросов DNS для поисковых систем, для которых был применён Безопасный поиск", "average_processing_time": "Среднее время обработки запроса", + "processing_time": "Время обработки", "average_processing_time_hint": "Среднее время для обработки запроса DNS в миллисекундах", "block_domain_use_filters_and_hosts": "Блокировать домены с использованием фильтров и файлов hosts", "filters_block_toggle_hint": "Вы можете настроить правила блокировки в «Фильтрах».", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Настроен в {{path}}", "test_upstream_btn": "Тест upstream серверов", "upstreams": "Upstreams", + "upstream": "Upstream-сервер", "apply_btn": "Применить", "disabled_filtering_toast": "Фильтрация выкл.", "enabled_filtering_toast": "Фильтрация вкл.", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: специальное значение, хранить записи <0>A с upstream-сервера", "rewrite_AAAA": "<0>AAAA: специальное значение, хранить записи <0>AAAA с upstream-сервера", "disable_ipv6": "Отключить обработку IPv6-адресов", - "disable_ipv6_desc": "Игнорировать все DNS-запросы адресов IPv6 (тип AAAA).", + "disable_ipv6_desc": "Игнорировать все DNS-запросы адресов IPv6 (тип AAAA) и удалять IPv6-данные из ответов типа HTTPS.", "fastest_addr": "Самый быстрый IP-адрес", "fastest_addr_desc": "Опросить все DNS-серверы и вернуть самый быстрый IP-адрес из полученных ответов. Это замедлит DNS-запросы, так как нужно будет дождаться ответов со всех DNS-серверов, но улучшит соединение.", "autofix_warning_text": "При нажатии «Исправить» AdGuard Home настроит вашу систему на использование DNS-сервера AdGuard Home.", @@ -676,5 +683,37 @@ "protection_section_label": "Защита", "log_and_stats_section_label": "Журнал запросов и статистика", "ignore_query_log": "Игнорировать этого клиента в журнале запросов", - "ignore_statistics": "Игнорировать этого клиента в статистике" + "ignore_statistics": "Игнорировать этого клиента в статистике", + "schedule_services": "Пауза блокировки сервисов", + "schedule_services_desc": "Настройка расписания паузы фильтра блокировки сервисов", + "schedule_services_desc_client": "Настройка расписания паузы фильтра блокировки сервисов для данного клиента", + "schedule_desc": "Установка периодов паузы блокировки сервисов", + "schedule_invalid_select": "Время начала должно быть до времени окончания", + "schedule_select_days": "Выбрать дни", + "schedule_timezone": "Выберите часовой пояс", + "schedule_current_timezone": "Текущий часовой пояс: {{value}}", + "schedule_time_all_day": "Весь день", + "schedule_modal_description": "Это расписание заменит все существующие расписания для того же дня недели. Каждый день недели может иметь только один период паузы блокировки.", + "schedule_modal_time_off": "Блокировка сервисов отключена:", + "schedule_new": "Новое расписание", + "schedule_edit": "Изменить расписание", + "schedule_save": "Сохранить расписание", + "schedule_add": "Добавить расписание", + "schedule_remove": "Удалить расписание", + "schedule_from": "С", + "schedule_to": "До", + "sunday": "Воскресенье", + "monday": "Понедельник", + "tuesday": "Вторник", + "wednesday": "Среда", + "thursday": "Четверг", + "friday": "Пятница", + "saturday": "Суббота", + "sunday_short": "Вс", + "monday_short": "Пн", + "tuesday_short": "Вт", + "wednesday_short": "Ср", + "thursday_short": "Чт", + "friday_short": "Пт", + "saturday_short": "Сб" } diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json index 9d14463a..2ee469ab 100644 --- a/client/src/__locales/si-lk.json +++ b/client/src/__locales/si-lk.json @@ -4,20 +4,20 @@ "parallel_requests": "සමාන්තර ඉල්ලීම්", "load_balancing": "ධාරිතාව තුලනය", "local_ptr_title": "පෞද්ගලික ප්‍රතිවර්ත ව.නා.ප. සේවාදායක", - "local_ptr_desc": "ස්ථානීය PTR විමසුම් සඳහා ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන ව.නා.ප. සේවාදායක. මෙම සේවාදායක පුද්ගලික අ.ජා.කෙ. ලිපින සහිත අනුග්‍රාහකවල සත්කාරක නාම විසඳීමට භාවිතා කරයි, උදාහරණයක් ලෙස ප්‍රතිවර්ත ව.නා.ප. භාවිතයෙන් \"192.168.12.34\". නැති නම්, ඇඩ්ගාර්ඩ් හෝම් හි ලිපින සඳහා හැරුනු විට ඔබගේ මෙහෙයුම් පද්ධතියේ පෙරනිමි ව.නා.ප. විසදුම්වල ලිපින භාවිතා කරයි.", - "local_ptr_default_resolver": "පෙරනිමි ලෙස, ඇඩ්ගාර්ඩ් හෝම් පහත ප්‍රතිවර්තත ව.නා.ප. පිළිවිසඳු භාවිතා කරයි: {{ip}}.", + "local_ptr_desc": "ස්ථානීය PTR විමසුම් සඳහා ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන ව.නා.ප. සේවාදායක. මෙම සේවාදායක පුද්ගලික අ.ජා.කෙ. ලිපින පරාසවල PTR විමසුම් විසඳීමට භාවිතා කරයි, උදාහරණයක් ලෙස ප්‍රතිවර්ත ව.නා.ප. භාවිතයෙන් \"192.168.12.34\". මෙය සකසා නැති නම්, ඇඩ්ගාර්ඩ් හෝම් හි ලිපින සඳහා හැරුනු විට ඔබගේ මෙහෙයුම් පද්ධතියේ පෙරනිමි ව.නා.ප. විසදුම්වල ලිපින භාවිතා කරයි.", + "local_ptr_default_resolver": "පෙරනිමි පරිදි, ඇඩ්ගාර්ඩ් හෝම් පහත ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතා කරයි: {{ip}}.", "local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු නිශ්චය කරගත නොහැකි විය.", "local_ptr_placeholder": "පේළියකට එක් සේවාදායක ලිපිනය බැගින් යොදන්න", "resolve_clients_title": "අනුග්‍රාහකවල අ.ජා.කෙ. ලිපින ප්‍රතිවර්ත විසඳීම සබල කරන්න", "use_private_ptr_resolvers_title": "පෞද්. ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතය", - "check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක සඳහා පරීක්‍ෂා කරන්න", + "check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක පරීක්‍ෂා කරන්න", "save_config": "වින්‍යාසය සුරකින්න", "enabled_dhcp": "ග.ධා.වි.කෙ. සේවාදායකය සබල කෙරිණි", "disabled_dhcp": "ග.ධා.වි.කෙ. සේවාදායකය අබල කෙරිණි", "unavailable_dhcp": "ග.ධා.වි.කෙ. නැත", "unavailable_dhcp_desc": "ඇඩ්ගාර්ඩ් හෝම් හට ඔබගේ මෙහෙයුම් පද්ධතියේ ග.ධා.වි.කෙ. සේවාදායකයක් ධාවනය කිරීමට නොහැකිය", "dhcp_title": "ග.ධා.වි.කෙ. සේවාදායකය (පර්යේෂණාත්මක!)", - "dhcp_description": "ඔබගේ මාර්ගකාරකය ග.ධා.වි.කෙ. (DHCP) සැකසුම් ලබා නොදෙන්නේ නම්, ඔබට ඇඩ්ගාර්ඩ් හි තිළෑලි ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කළ හැකිය.", + "dhcp_description": "ඔබගේ මාර්ගකාරකය ග.ධා.වි.කෙ. (DHCP) සැකසුම් ලබා නොදෙන්නේ නම්, ඔබට ඇඩ්ගාර්ඩ් තිළෑලි ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කිරීමට හැකිය.", "dhcp_enable": "ග.ධා.වි.කෙ. සේවාදායකය සබල කරන්න", "dhcp_disable": "ග.ධා.වි.කෙ. සේවාදායකය අබල කරන්න", "dhcp_not_found": "ඇඩ්ගාර්ඩ් හෝම් සඳහා ජාලයෙහි කිසිදු ක්‍රියාත්මක ග.ධා.වි.කෙ. සේවාදායකයක් හමු නොවූ නිසා තිළෑලි සේවාදායකය සබල කිරීම ආරක්‍ෂිත වේ. කෙසේ වෙතත්, ස්වයංක්‍රීය ඒෂණය ඉතා නිවැරදි නොවිය හැකි බැවින් ඔබ එය අතින් නැවත පරීක්‍ෂා කළ යුතුය.", @@ -26,11 +26,11 @@ "dhcp_static_leases": "ස්ථිර ග.ධා.වි.කෙ. කල්පැවරීම", "dhcp_leases_not_found": "ග.ධා.වි.කෙ. කල්පැවරීම් නැත", "dhcp_config_saved": "ග.ධා.වි.කෙ. වින්‍යාසය සාර්ථකව සුරකින ලදි", - "dhcp_ipv4_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 4 සැකසුම්", - "dhcp_ipv6_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 6 සැකසුම්", + "dhcp_ipv4_settings": "ග.ධා.වි.කෙ. IPv4 සැකසුම්", + "dhcp_ipv6_settings": "ග.ධා.වි.කෙ. IPv6 සැකසුම්", "form_error_required": "ඇවැසි ක්‍ෂේත්‍රයකි", "form_error_ip4_format": "IPv4 ලිපිනය වලංගු නොවේ", - "form_error_ip6_format": "වලංගු නොවන අ.ජා.කෙ.6 ලිපිනයකි", + "form_error_ip6_format": "වලංගු නොවන IPv6 ලිපිනයකි", "form_error_ip_format": "අ.ජා.කෙ. (IP) ලිපිනය වලංගු නොවේ", "form_error_mac_format": "මා.ප්‍ර.පා. ලිපිනය වලංගු නොවේ", "form_error_client_id_format": "අනුග්‍රාහකයේ හැඳු. වලංගු නොවේ", @@ -41,7 +41,7 @@ "lower_range_start_error": "පරාසය ආරම්භයට වඩා අඩු විය යුතුය", "greater_range_start_error": "පරාසය ආරම්භයට වඩා වැඩි විය යුතුය", "subnet_error": "ලිපින එක් අනුජාලයක තිබිය යුතුය", - "dhcp_form_range_title": "අ.ජා. කෙ. (IP) ලිපින පරාසය", + "dhcp_form_range_title": "අ.ජා.කෙ. (IP) ලිපින පරාසය", "dhcp_form_range_start": "පරාසය ආරම්භය", "dhcp_form_range_end": "පරාසය අවසානය", "dhcp_form_lease_title": "ග.ධා.වි.කෙ. කල්පැවරීම (තත්. වලින්)", @@ -50,7 +50,7 @@ "dhcp_hardware_address": "දෘඩාංග ලිපිනය", "dhcp_ip_addresses": "අ.ජා.කෙ. (IP) ලිපින", "ip": "අ.ජා.කෙ. (IP)", - "dhcp_table_hostname": "ධාරක නාමය", + "dhcp_table_hostname": "සත්කාරක නාමය", "dhcp_table_expires": "කල් ඉකුත් වීම", "dhcp_warning": "ඔබට කෙසේ හෝ ග.ධා.වි.කෙ. සේවාදායකය සබල කිරීමට අවශ්‍ය නම්, ඔබගේ ජාලයේ වෙනත් ක්‍රියාකාරී ග.ධා.වි.කෙ. සේවාදායකයක් නැති බව තහවුරු කරගන්න. මෙය සම්බන්ධිත උපාංග සඳහා අන්තර්ජාලය බිඳ දැමිය හැකිය!", "dhcp_error": "ජාලයේ තවත් ක්‍රියාත්මක ග.ධා.වි.කෙ. සේවාදායකයක් තිබේද යන්න නිශ්චය කළ නොහැකි විය", @@ -62,13 +62,13 @@ "dhcp_static_leases_not_found": "ග.ධා.වි.කෙ. ස්ථිර කල්පැවරීම් නැත", "dhcp_add_static_lease": "ස්ථිර කල්පැවරීමක් යොදන්න", "dhcp_reset_leases": "කල්පැවරීම් යළි සකසන්න", - "dhcp_reset_leases_confirm": "සියළු කල්පැවරීම් යළි සැකසීමට වුවමනා ද?", + "dhcp_reset_leases_confirm": "සියළුම කල්පැවරීම් යළි සැකසීමට වුවමනා ද?", "dhcp_reset_leases_success": "ග.ධා.වි.කෙ. කල්පැවරීම් යළි සැකසිණි", "dhcp_reset": "ග.ධා.වි.කෙ. වින්‍යාසය යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?", "country": "රට", "city": "නගරය", "delete_confirm": "\"{{key}}\" මකා දැමීමට අවශ්‍ය බව ඔබට විශ්වාසද?", - "form_enter_hostname": "ධාරක නාමය ඇතුල් කරන්න", + "form_enter_hostname": "සත්කාරක නාමය යොදන්න", "error_details": "දෝෂ විස්තර", "response_details": "ප්‍රතිචාරයෙහි විස්තර", "request_details": "ඉල්ලීමෙහි විස්තර", @@ -116,19 +116,20 @@ "number_of_dns_query_days": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන", "number_of_dns_query_days_plural": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන", "number_of_dns_query_24_hours": "පසුගිය පැය 24 සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන", - "number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ ධාරක වාරණ ලැයිස්තු මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන", + "number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ සත්කාරක වාරණ ලැයිස්තු මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන", "number_of_dns_query_blocked_24_hours_by_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්‍ෂණ ඒකකය මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන", "number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි ගණන", - "enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ", - "number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්‍ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන", + "enforced_save_search": "ආරක්‍ෂිත සෙවීම බලාත්මක කළ", + "number_of_dns_query_to_safe_search": "ආරක්‍ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්‍ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන", "average_processing_time": "සාමාන්‍ය සැකසුම් කාලය", + "processing_time": "සැකසුම් කාලය", "average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්‍ය කාලය මිලි තත්පර වලින්", "block_domain_use_filters_and_hosts": "පෙරහන් හා සත්කාරක ගොනු භාවිතයෙන් වසම් අවහිර කරන්න", "filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති පෙරහන් තුළ පිහිටුවිය හැකිය.", "use_adguard_browsing_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව භාවිතා කරන්න", "use_adguard_browsing_sec_hint": "ඇඩ්ගාර්ඩ් හෝම් විසින් පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මගින් වසම අවහිර කර ඇත්දැයි පරීක්‍ෂා කරයි. එය සිදු කිරීමට රහස්‍යතා-හිතකාමී බැලීමේ යෙ.ක්‍ර.මු. භාවිතා කෙරේ: වසමේ කෙටි උපසර්ගයක SHA256 පූරකයක් පමණක් සේවාදායකය වෙත යවනු ලැබේ.", "use_adguard_parental": "ඇඩ්ගාර්ඩ් දෙමාපිය පාලන වියමන සේවාව භාවිතා කරන්න", - "use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.", + "use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්‍ෂා කරනු ඇත. එය පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.", "enforce_safe_search": "ආරක්‍ෂිත සෙවුම භාවිතා කරන්න", "enforce_save_search_hint": "ඇඩ්ගාර්ඩ් හෝම් පහත සෙවුම් යන්ත්‍ර තුළ ආරක්‍ෂිත සෙවුම බලාත්මක කරනු ඇත: ගූගල්, යූටියුබ්, බින්ග්, ඩක්ඩක්ගෝ, යාන්ඩෙක්ස් සහ පික්සාබේ.", "no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත", @@ -144,6 +145,7 @@ "upstream_dns": "Upstream ව.නා.ප. සේවාදායක", "upstream_dns_help": "පේළියකට එක් සේවාදායක ලිපිනය බැගින් ඇතුල් කරන්න. upstream ව.නා.ප. (DNS) \n සේවාදායක වින්‍යාසගත කිරීම ගැන තව දැනගන්න.", "upstream_dns_configured_in_file": "{{path}} හි වින්‍යාසගත කර ඇත", + "test_upstream_btn": "අත්හදා බලන්න", "apply_btn": "යොදන්න", "disabled_filtering_toast": "පෙරීම අබල කෙරිණි", "enabled_filtering_toast": "පෙරීම සබල කෙරිණි", @@ -164,7 +166,7 @@ "edit_table_action": "සංස්කරණය කරන්න", "delete_table_action": "මකන්න", "elapsed": "ගත වූ කාලය", - "filters_and_hosts_hint": "ඇඩ්ගාර්ඩ් හෝම් මූලික දැන්වීම් වාරණ නීති සහ ධාරක ගොනු පද ගැලපුම් තේරුම් ගනී.", + "filters_and_hosts_hint": "ඇඩ්ගාර්ඩ් හෝම් මූලික දැන්වීම් වාරණ නීති සහ සත්කාරක ගොනු පද ගැලපුම් තේරුම් ගනී.", "no_blocklist_added": "අවහිර කිරීමේ ලැයිස්තු එකතු කර නැත", "no_whitelist_added": "ඉඩ දීමේ ලැයිස්තු එකතු කර නැත", "add_blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුවක් එකතු කරන්න", @@ -184,11 +186,11 @@ "form_error_url_format": "වලංගු නොවන ඒ.ස.නි.(URL) ආකෘතියකි", "form_error_url_or_path_format": "වලංගු නොවන ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයකි", "custom_filter_rules": "අභිරුචි පෙරීමේ නීති", - "custom_filter_rules_hint": "පේළියකට එක් නීතියක් බැගින් ඇතුල් කරන්න. ඔබට දැන්වීම් අවහිර කිරීමේ නීති හෝ ධාරක ගොනු පද ගැලපුම් භාවිතා කළ හැකිය.", + "custom_filter_rules_hint": "පේළියකට එක් නීතියක් බැගින් ඇතුල් කරන්න. ඔබට දැන්වීම් අවහිර කිරීමේ නීති හෝ සත්කාරක ගොනු පද ගැලපුම් භාවිතා කිරීමට හැකිය.", "system_host_files": "පද්ධතියේ සත්කාරක ගොනු", "examples_title": "උදාහරණ", - "example_meaning_filter_block": "උදාහරණය.ලංකා වසමට සහ එහි සියළු උප වසම් වලට ප්‍රවේශය අවහිර කරයි;", - "example_meaning_filter_whitelist": "උදාහරණය.ලංකා වසමට සහ එහි සියළු උප වසම් වලට ප්‍රවේශය අනවහිර කරයි;", + "example_meaning_filter_block": "උදාහරණය.ලංකා වසමට සහ එහි සියළුම උප වසම් වලට ප්‍රවේශය අවහිර කරයි;", + "example_meaning_filter_whitelist": "උදාහරණය.ලංකා වසමට සහ එහි සියළුම උප වසම් වලට ප්‍රවේශය අනවහිර කරයි;", "example_meaning_host_block": "උදාහරණය.ලංකා වසම සඳහා 127.0.0.1 සමඟ ප්‍රතිචාර දක්වයි (නමුත් එහි උප ලිපින සඳහා නොවේ);", "example_comment": "! මෙතැන අදහස් දැක්වීමක්.", "example_comment_meaning": "අදහසක්;", @@ -232,7 +234,7 @@ "updated_custom_filtering_toast": "අභිරුචි නීති සාර්ථකව සුරකින ලදි", "rule_removed_from_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති තුළින් නීතියක් ඉවත් කෙරිණි: {{rule}}", "rule_added_to_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති තුළට මෙම නීතිය එකතු කෙරිණි: {{rule}}", - "query_log_response_status": "තත්වය: {{value}}", + "query_log_response_status": "තත්‍වය: {{value}}", "query_log_filtered": "{{filter}} මගින් පෙරිණි", "query_log_confirm_clear": "සමස්ථ විමසුම් සටහන හිස් කිරීමට ඇවැසි බව ඔබට විශ්වාසද?", "query_log_cleared": "විමසුම් සටහන සාර්ථකව හිස් කර ඇත", @@ -255,8 +257,8 @@ "refused": "REFUSED", "null_ip": "අභිශූන්‍යය අ.ජා.කෙ.", "custom_ip": "අභිරුචි අ.ජා.කෙ.", - "blocking_ipv4": "අ.ජා.කෙ.4 අවහිර කිරීම", - "blocking_ipv6": "අ.ජා.කෙ.6 අවහිර කිරීම", + "blocking_ipv4": "IPv4 අවහිර කිරීම", + "blocking_ipv6": "IPv6 අවහිර කිරීම", "dnscrypt": "DNSCrypt", "dns_over_https": "HTTPS-මගින්-ව.නා.ප.", "dns_over_tls": "TLS-මගින්-ව.නා.ප.", @@ -276,7 +278,7 @@ "rate_limit_desc": "එක් අනුග්‍රාහකයකට ඉඩ දී ඇති තත්පරයට ඉල්ලීම් ගණන. එය 0 ලෙස සැකසීම යනුවෙන් අදහස් කරන්නේ සීමාවක් නැති බවයි.", "blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය", "blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය", - "blocking_mode_default": "පොදු: දැන්වීම් අවහිර කරන ආකාරයේ නීතියක් මගින් අවහිර කළ විට REFUSED සමඟ ප්‍රතිචාර දක්වයි; /etc/host-style ආකාරයේ නීතියක් මගින් අවහිර කළ විට නීතියේ දක්වා ඇති අ.ජා. කෙ. ලිපිනය සමඟ ප්‍රතිචාර දක්වයි", + "blocking_mode_default": "පොදු: දැන්වීම් අවහිර කරන ආකාරයේ නීතියක් මගින් අවහිර කළ විට REFUSED සමඟ ප්‍රතිචාර දක්වයි; /etc/host-style ආකාරයේ නීතියක් මගින් අවහිර කළ විට නීතියේ දක්වා ඇති අ.ජා.කෙ. ලිපිනය සමඟ ප්‍රතිචාර දක්වයි", "blocking_mode_refused": "REFUSED: REFUSED කේතය සමඟ ප්‍රතිචාර දක්වයි", "blocking_mode_nxdomain": "නොපවතින වසම: NXDOMAIN කේතය සමඟ ප්‍රතිචාර දක්වයි", "blocking_mode_null_ip": "අභිශූන්‍යය අ.ජා.කෙ.: ශුන්‍ය අ.ජා.කෙ. ලිපිනය සමඟ ප්‍රතිචාර දක්වයි (A සඳහා 0.0.0.0; AAAA සඳහා ::)", @@ -284,7 +286,7 @@ "theme_auto": "ස්වයං", "theme_light": "දීප්ත", "theme_dark": "අඳුරු", - "upstream_dns_client_desc": "ඔබ මෙම ක්ෂේත්‍රය හිස්ව තබා ගන්නේ නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් <0>ව.නා.ප. සැකසුම් හි වින්‍යාසගත කර ඇති සේවාදායක භාවිතා කරනු ඇත.", + "upstream_dns_client_desc": "ඔබ මෙම ක්‍ෂේත්‍රය හිස්ව තබා ගන්නේ නම්, <0>ව.නා.ප. සැකසුම් හි වින්‍යාසගත කර ඇති සේවාදායක ඇඩ්ගාර්ඩ් හෝම් විසින් භාවිතා කරනු ඇත.", "tracker_source": "ලුහුබැඳීම් මූලාශ්‍රය", "source_label": "මූලාශ්‍රය", "found_in_known_domain_db": "දැනුවත් වසම් දත්ත ගබඩාවේ හමු විය.", @@ -294,38 +296,38 @@ "unknown_filter": "{{filterId}} නොදන්නා පෙරහනකි", "known_tracker": "දැනුවත් ලුහුබැඳීමකි", "install_welcome_title": "ඇඩ්ගාර්ඩ් හෝම් වෙත සාදරයෙන් පිළිගනිමු!", - "install_welcome_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු ජාලය පුරා ඇති දැන්වීම් සහ ලුහුබැඳීම අවහිර කරන ව.නා.ප. සේවාදායකයකි. ඔබගේ මුළු ජාලය සහ සියළුම උපාංග පාලනය කිරීමට ඉඩ සලසා දීම එහි පරමාර්ථය යි, එයට අනුග්‍රාහක පාර්ශවීය වැඩසටහනක් භාවිතා කිරීම අවශ්‍ය නොවේ.", + "install_welcome_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු ජාලය පුරා දැන්වීම් සහ ලුහුබැඳීම අවහිර කරන ව.නා.ප. සේවාදායකයකි. ඔබගේ සමස්ත ජාලය සහ සියළුම උපාංග පාලනයට ඉඩ සලසා දීම එහි පරමාර්ථය යි, එයට අනුග්‍රාහක පාර්ශ්ව වැඩසටහනක් වුවමනා නොවේ.", "install_settings_title": "පරිපාලක වියමන අතුරු මුහුණත", "install_settings_listen": "සවන් දෙන අතුරු මුහුණත", "install_settings_port": "තොට", "install_settings_interface_link": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වියමන අතුරු මුහුණතට පහත ලිපින වලින් ප්‍රවේශ වීමට හැකිය:", "form_error_port": "වලංගු තොටක අගයක් යොදන්න", "install_settings_dns": "ව.නා.ප. සේවාදායකය", - "install_settings_dns_desc": "පහත ලිපිනයන්හි ව.නා.ප. සේවාදායකය භාවිතා කිරීම සඳහා ඔබගේ උපාංග හෝ මාර්ගකාරකය වින්‍යාසගත කිරීමට අවශ්‍ය වනු ඇත:", - "install_settings_all_interfaces": "සියළු අතුරුමුහුණත්", + "install_settings_dns_desc": "ව.නා.ප. සේවාදායකය පහත ලිපිනවල භාවිතා කිරීම සඳහා ඔබගේ උපාංග හෝ මාර්ගකාරකය වින්‍යාසගත කිරීමට අවශ්‍ය වනු ඇත:", + "install_settings_all_interfaces": "සියළුම අතුරුමුහුණත්", "install_auth_title": "සත්‍යාපනය", - "install_auth_desc": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලන වියමන අතුරු මුහුණතට මුරපද සත්‍යාපනය වින්‍යාසගත කළ යුතුය. එය ඔබගේ ස්ථානීය ජාල‌යෙන් පමණක් ප්‍රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්‍රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.", - "install_auth_username": "පරිශීලක නාමය", + "install_auth_desc": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලන වියමන අතුරු මුහුණතට මුරපද සත්‍යාපනය වින්‍යාසගත කළ යුතුය. ඔබගේ ස්ථානීය ජාල‌යෙන් පමණක් ප්‍රවේශ වීමට හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්‍රවේශයකින් ආරක්‍ෂා කර ගැනීම වැදගත් ය.", + "install_auth_username": "පරිශ්‍රීලක නාමය", "install_auth_password": "මුරපදය", "install_auth_confirm": "මුරපදය තහවුරු කරන්න", - "install_auth_username_enter": "පරිශීලක නාමය යොදන්න", + "install_auth_username_enter": "පරිශ්‍රීලක නාමය යොදන්න", "install_auth_password_enter": "මුරපදය ඇතුල් කරන්න", "install_step": "පියවර", "install_devices_title": "ඔබගේ උපාංග වින්‍යාසගත කරන්න", - "install_devices_desc": "ඇඩ්ගාර්ඩ් හෝම් භාවිතා කිරීම ආරම්භයට, ඔබගේ උපාංග එය පරිශ්‍රීලනයට වින්‍යාසගත කළ යුතුය.", + "install_devices_desc": "ඇඩ්ගාර්ඩ් හෝම් භාවිතා කිරීමට, ඔබගේ උපාංග එය පරිශ්‍රීලනයට වින්‍යාසගත කළ යුතුය.", "install_submit_title": "සුභ පැතුම්!", - "install_submit_desc": "පිහිටුවීමේ ක්‍රියා පටිපාටිය අවසන් වී ඇති අතර ඔබ දැන් ඇඩ්ගාර්ඩ් හෝම් භාවිතය ආරම්භ කිරීමට සූදානම්ය.", + "install_submit_desc": "පිහිටුවීමේ ක්‍රියා පටිපාටිය අවසන් වී ඇති අතර ඔබ දැන් ඇඩ්ගාර්ඩ් හෝම් භාවිතා කිරීමට සූදානම් ය.", "install_devices_router": "මාර්ගකාරකය", "install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධිත සියළුම උපාංග ස්වයංක්‍රීයව ආවරණය කරන අතර ඔබට ඒ සෑම එකක්ම අතින් වින්‍යාසගත කිරීමට අවශ්‍ය නොවේ.", "install_devices_address": "ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය පහත ලිපිනයන්ට සවන් දෙමින් පවතී", - "install_devices_router_list_1": "ඔබගේ මාර්ගකාරකයෙහි අභිප්‍රේත විවෘත කරන්න. සාමාන්‍යයෙන්, එය ඔබගේ අතිරික්සුවෙන් ඒ.ස.නි.(URL) ක් හරහා (http://192.168.0.1/ හෝ http://192.168.1.1/ වැනි) ප්‍රවේශ විය හැකිය. මුරපදය ඇතුල් කිරීමට සිදු විය හැකි නමුත් එය මතක නැතිනම් බොහෝ විට මාර්ගකාරකයේ බොත්තමක් එබීමෙන් මුරපදය නැවත සැකසීමට හැකිය. නමුත් මෙම ක්‍රියා පටිපාටිය තෝරා ගන්නේ නම්, බොහෝ විට ඔබගේ මාර්ගකාරකයේ සමස්ථ වින්‍යාසය අහිමි වනු ඇති බව මතක තබා ගන්න. මෙය පිහිටුවීමට ඔබගේ මාර්ගකාරකයට යෙදුමක් ඇවැසි නම්, කරුණාකර එය ඔබගේ පරිගණකයේ හෝ දුරකථනයේ ස්ථාපනය කර මාර්ගකාරකයේ සැකසුම් වෙත ප්‍රවේශ වීමට භාවිතා කරන්න.", - "install_devices_router_list_2": "ග.ධා.වි.කෙ. (DHCP)/ ව.නා.ප. (DNS) සැකසුම් සොයා ගන්න. අංක කට්ටල දෙකකට හෝ තුනකට ඉඩ දෙන ක්ෂේත්‍රයක් අසල ඇති ව.නා.ප. අකුරු බලන්න, සෑම එකක්ම ඉලක්කම් එකේ සිට තුන දක්වා කාණ්ඩ හතරකට බෙදා ඇත.", + "install_devices_router_list_1": "ඔබගේ මාර්ගකාරකයෙහි අභිප්‍රේත විවෘත කරන්න. සාමාන්‍යයෙන්, එය ඔබගේ අතිරික්සුවෙන් ඒ.ස.නි.(URL) ක් හරහා (http://192.168.0.1/ හෝ http://192.168.1.1/ වැනි) ප්‍රවේශ වීමට හැකිය. මුරපදය ඇතුල් කිරීමට සිදු විය හැකි නමුත් එය මතක නැතිනම් බොහෝ විට මාර්ගකාරකයේ බොත්තමක් එබීමෙන් මුරපදය නැවත සැකසීමට හැකිය. නමුත් මෙම ක්‍රියා පටිපාටිය තෝරා ගන්නේ නම්, බොහෝ විට ඔබගේ මාර්ගකාරකයේ සමස්ථ වින්‍යාසය අහිමි වනු ඇති බව මතක තබා ගන්න. මෙය පිහිටුවීමට ඔබගේ මාර්ගකාරකයට යෙදුමක් වුවමනා නම්, කරුණාකර එය ඔබගේ පරිගණකයේ හෝ දුරකථනයේ ස්ථාපනය කර මාර්ගකාරකයේ සැකසුම් වෙත ප්‍රවේශ වීමට භාවිතා කරන්න.", + "install_devices_router_list_2": "ග.ධා.වි.කෙ. (DHCP)/ ව.නා.ප. (DNS) සැකසුම් සොයා ගන්න. අංක කට්ටල දෙකකට හෝ තුනකට ඉඩ දෙන ක්‍ෂේත්‍රයක් අසල ඇති ව.නා.ප. අකුරු බලන්න, සෑම එකක්ම ඉලක්කම් එකේ සිට තුන දක්වා කාණ්ඩ හතරකට බෙදා ඇත.", "install_devices_router_list_3": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින එහි ඇතුල් කරන්න.", - "install_devices_router_list_4": "සමහර වර්ගයේ මාර්ගකාරක වල අභිරුචි ව.නා.ප. සේවාදායකයක් සැකසීමට නොහැකිය. මෙම අවස්ථාවේදී ඇඩ්ගාර්ඩ් හෝම් <0>ග.ධා.වි.කෙ. සේවාදායකයක් ලෙස පිහිටුවන්නේ නම් එය උපකාර වනු ඇත. එසේ නැතිනම්, ඔබගේ විශේෂිත මාර්ගකාරකය සඳහා වූ ව.නා.ප. සේවාදායක රිසිකරණය කරන්නේ කෙසේද යන්න පිළිබඳ අත්පොත පරීක්‍ෂා කළ යුතුය.", + "install_devices_router_list_4": "සමහර මාර්ගකාරක වර්ගවල අභිරුචි ව.නා.ප. සේවාදායකයක් සැකසීමට නොහැකිය. මෙම අවස්ථාවේ දී ඇඩ්ගාර්ඩ් හෝම් <0>ග.ධා.වි.කෙ. සේවාදායකයක් ලෙස පිහිටුවන්නේ නම් එය විසඳුමක් වනු ඇත. එසේ නැතිනම්, ඔබගේ විශේෂිත මාර්ගකාරකයේ අත්පොත පරීක්‍ෂා කර අභිරුචි ව.නා.ප. සේවාදායක යොදන ආකාරය සොයා ගත යුතුය.", "install_devices_windows_list_1": "පාලන වට්ටෝරුව හෝ වින්ඩෝස් සෙවුම හරහා පාලන මඬල අරින්න.", "install_devices_windows_list_2": "ජාල සහ අන්තර්ජාල ප්‍රවර්ගයට ගොස් පසුව ජාල සහ බෙදාගැනීමේ මධ්‍යස්ථානය වෙත යන්න.", "install_devices_windows_list_3": "වම් තීරුවෙහි \"උපයුක්තක‌‌‌යෙහි සැකසුම් වෙනස් කිරීම\" ඔබන්න.", - "install_devices_windows_list_4": "ඔබගේ ක්‍රියාකාරී සම්බන්ධතාවය මත දකුණු-ක්ලික් කර ගුණාංග තෝරන්න.", + "install_devices_windows_list_4": "ඔබගේ සක්‍රිය සම්බන්ධතාවය මත දකුණින් ඔබා ගුණාංග තෝරන්න.", "install_devices_windows_list_5": "ලැයිස්තුවෙන් \"අන්තර්ජාල කෙටුම්පත් අනුවාදය 4 (TCP/IPv4)\" (හෝ, IPv6 සඳහා, \"අන්තර්ජාල කෙටුම්පත් අනුවාදය 6 (TCP/IPv6)\") සොයාගෙන එය තෝරා ඉන්පසු ගුණාංග මත නැවත ඔබන්න.", "install_devices_windows_list_6": "'පහත සඳහන් ව.නා.ප. සේවාදායක ලිපින භාවිතා කරන්න' යන්න තෝරා ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින ඇතුල් කරන්න.", "install_devices_macos_list_1": "ඇපල් නිරූපකය එබීමෙන් පසු පද්ධතියේ අභිප්‍රේත වෙත යන්න.", @@ -334,27 +336,27 @@ "install_devices_macos_list_4": "ව.නා.ප. (DNS) තීරුව තෝරා ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින ඇතුල් කරන්න.", "install_devices_android_list_1": "ඇන්ඩ්‍රොයිඩ් මුල් තිරයෙන්, සැකසුම් මත තට්ටු කරන්න.", "install_devices_android_list_2": "වට්ටෝරුවෙහි වයි-ෆයි මත තට්ටු කරන්න. පවතින සියළුම ජාල ලේඛන ගතවී තිබෙන තිරය පෙන්වනු ඇත (ජංගම සම්බන්ධතාවය සඳහා අභිරුචි ව.නා.ප. සැකසීමට නොහැකිය).", - "install_devices_android_list_3": "සම්බන්ධිත ජාලය මත දිගු වේලාවක් ඔබන්න, ඉන්පසුව ජාලය වෙනස් කිරීම මත තට්ටු කරන්න.", - "install_devices_android_list_4": "ඔබට සමහර උපාංගවල සියළු සැකසුම් බැලීමට \"වැඩිදුර\" සඳහා වූ කොටුව සලකුණු කිරීමට අවශ්‍ය විය හැකිය. එමෙන්ම ඔබගේ ඇන්ඩ්‍රොයිඩ් ව.නා.ප. (DNS) සැකසුම් වෙනස් කිරීමට අ.ජා.කෙ. (IP) සැකසුම්, ග.ධා.වි.කෙ. (DHCP) සිට ස්ථිතික වෙත මාරු කළ යුතුය.", + "install_devices_android_list_3": "සම්බන්ධිත ජාලය මත මද වේලාවක් ඔබාගෙන ඉන්න, ඉන්පසුව ජාලය වෙනස් කිරීම මත තට්ටු කරන්න.", + "install_devices_android_list_4": "ඔබට සමහර උපාංගවල සියළුම සැකසුම් බැලීමට \"වැඩිදුර\" සඳහා වූ කොටුව සලකුණු කිරීමට අවශ්‍ය විය හැකිය. එමෙන්ම ග.ධා.වි.කෙ. (DHCP) සිට ස්ථිතික වෙත අ.ජා.කෙ. (IP) සැකසුම් මාරු කිරීමෙන් ඔබගේ ඇන්ඩ්‍රොයිඩ් ව.නා.ප. (DNS) සැකසුම් වෙනස් කිරීමට හැකිය.", "install_devices_android_list_5": "ව.නා.ප. 1 සහ ව.නා.ප. 2 පිහිටුවීම් අගයන් ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින වලට වෙනස් කරන්න.", - "install_devices_ios_list_1": "මුල් තිරයේ සිට, සැකසුම් මත තට්ටු කරන්න.", - "install_devices_ios_list_2": "වම්පස මෙනුවෙහි වයි-ෆයි තෝරන්න (ජං. දු.ක. සඳහා ව.නා.ප. වින්‍යාසගත කිරීමට නොහැකිය).", - "install_devices_ios_list_3": "දැනට ක්‍රියාකාරී ජාලයයහෙි නම මත තට්ටු කරන්න.", - "install_devices_ios_list_4": "ව.නා.ප. (DNS) ක්ෂේත්‍රය තුළ ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින ඇතුල් කරන්න.", + "install_devices_ios_list_1": "මුල් තිරයෙන්, සැකසුම් මත තට්ටු කරන්න.", + "install_devices_ios_list_2": "වම්පස වට්ටෝරුවෙන් වයි-ෆයි තෝරන්න (ජංගම දුරකථන සඳහා ව.නා.ප. වින්‍යාසගත කිරීමට නොහැකිය).", + "install_devices_ios_list_3": "දැනට සක්‍රිය ජාලයේ නම මත තට්ටු කරන්න.", + "install_devices_ios_list_4": "ව.නා.ප. (DNS) ක්‍ෂේත්‍රය තුළ ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින ඇතුල් කරන්න.", "get_started": "පටන් ගන්න", "next": "ඊළඟ", - "open_dashboard": "උපකරණ පුවරුව විවෘත කරන්න", + "open_dashboard": "උපකරණ පුවරුව අරින්න", "install_saved": "සාර්ථකව සුරකින ලදි", "encryption_title": "සංකේතනය", - "encryption_desc": "ගුප්තකේතනය (HTTPS/QUIC/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වියමන අතුරු මුහුණත සහය දක්වයි", + "encryption_desc": "සංකේතනය (HTTPS/QUIC/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වියමන අතුරු මුහුණත සහය දක්වයි", "encryption_config_saved": "සංකේතන වින්‍යාසය සුරකින ලදි", "encryption_server": "සේවාදායක‌‌‌‌යේ නම", - "encryption_server_enter": "ඔබගේ වසම් නාමය ඇතුල් කරන්න", - "encryption_server_desc": "සැකසා ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් අනුග්‍රාහක හැඳුනුම් හඳුනා ගැනෙයි, සෘ.ද.ඉ. (DDR) විමසුම්වලට ප්‍රතිචාර දක්වයි, සහ අතිරේක සම්බන්ධතා වලංගුකරණය සිදු කරයි. නොඑසේ නම්, මෙම විශේෂාංග අබලව ඇත. සහතිකයේ තිබෙන ව.නා.ප. නම් වලින් එකකට ගැළපිය යුතුය.", + "encryption_server_enter": "වසමේ නම ඇතුල් කරන්න", + "encryption_server_desc": "සකසා ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් අනුග්‍රාහක හැඳුනුම් හඳුනා ගැනෙයි, සෘ.ද.ඉ. (DDR) විමසුම් වලට ප්‍රතිචාර දක්වයි, සහ අතිරේක සම්බන්ධතා වලංගුකරණය සිදු කරයි. නොඑසේ නම්, මෙම විශේෂාංග අබලව පවතී. සහතිකයේ අඩංගු ව.නා.ප. නම් වලින් එකකට ගැළපිය යුතුය.", "encryption_redirect": "ස්වයංක්‍රීයව HTTPS වෙත හරවා යවන්න", - "encryption_redirect_desc": "සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් ඔබව ස්වයංක්‍රීයව HTTP සිට HTTPS ලිපින වෙත හරවා යවනු ඇත.", + "encryption_redirect_desc": "සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් ඔබව ස්වයංක්‍රීයව HTTP වෙතින් HTTPS ලිපින වෙත හරවා යවනු ඇත.", "encryption_https": "HTTPS තොට", - "encryption_https_desc": "HTTPS තොට වින්‍යාසගත නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ප්‍රවේශ විය හැකි අතර එය '/dns-query' ස්ථානයේ HTTPS-මගින්-ව.නා.ප. ද ලබා දෙනු ඇත.", + "encryption_https_desc": "HTTPS තොට වින්‍යාසගත නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණතට HTTPS හරහා ප්‍රවේශ විය හැකි අතර එය '/dns-query' ස්ථානයේ HTTPS-මගින්-ව.නා.ප. ද ලබා දෙනු ඇත.", "encryption_dot": "TLS-මගින්-ව.නා.ප. තොට", "encryption_dot_desc": "මෙම තොට වින්‍යාසගත නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා TLS-මගින්-ව.නා.ප. සේවාදායකයක් ධාවනය කෙරේ.", "encryption_doq": "QUIC-මගින්-ව.නා.ප. තොට", @@ -362,19 +364,19 @@ "encryption_certificates": "සහතික", "encryption_certificates_desc": "සංකේතනය භාවිතයට, ඔබගේ වසම සඳහා වලංගු SSL සහතික දාමයක් සැපයිය යුතුය. <0>{{link}} වෙතින් නොමිලේ සහතිකයක් ලබා ගැනීමට හැකිය හෝ විශ්වාසදායක සහතික අධිකාරියකින් මිලදී ගන්න.", "encryption_certificates_input": "ඔබගේ PEM-කේතනය කළ සහතික පිටපත් කර මෙහි අලවන්න.", - "encryption_status": "තත්වය", + "encryption_status": "තත්‍වය", "encryption_expire": "කල් ඉකුත් වීම", "encryption_key": "පුද්ගලික යතුර", "encryption_key_input": "ඔබගේ සහතිකය සඳහා PEM-කේතනය කළ පුද්ගලික යතුර පිටපත් කර මෙහි අලවන්න.", "encryption_enable": "සංකේතනය සබල කරන්න (HTTPS, HTTPS-මගින්-ව.නා.ප. සහ TLS-මගින්-ව.නා.ප.)", - "encryption_enable_desc": "සංකේතනය සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ක්‍රියා කරනු ඇති අතර ව.නා.ප. සේවාදායකය HTTPS-මගින්-ව.නා.ප. සහ TLS-මගින්-ව.නා.ප. හරහා ලැබෙන ඉල්ලීම් සඳහා සවන් දෙනු ඇත.", + "encryption_enable_desc": "සංකේතනය සබල කළ විට, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ක්‍රියා කරන අතර ව.නා.ප. සේවාදායකය HTTPS-මගින්-ව.නා.ප. සහ TLS-මගින්-ව.නා.ප. හරහා ලැබෙන ඉල්ලීම් වලට සවන් දෙනු ඇත.", "encryption_chain_valid": "සහතික දාමය වලංගු ය", "encryption_chain_invalid": "සහතික දාමය වලංගු නොවේ", "encryption_key_valid": "මෙය වලංගු {{type}} පුද්ගලික යතුරකි", "encryption_key_invalid": "මෙය වලංගු නොවන {{type}} පුද්ගලික යතුරකි", "encryption_subject": "මාතෘකාව", "encryption_issuer": "නිකුත් කරන්නා", - "encryption_hostnames": "ධාරක නාම", + "encryption_hostnames": "සත්කාරක නාම", "encryption_reset": "සංකේතාංකන සැකසුම් යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?", "encryption_warning": "අවවාදයයි", "topline_expiring_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත්වීමට ආසන්න වී ඇත. <0>සංකේතන සැකසුම් යාවත්කාල කරන්න.", @@ -423,14 +425,14 @@ "clients_not_found": "අනුග්‍රාහක හමු නොවිණි", "client_confirm_delete": "\"{{key}}\" අනුග්‍රාහකය ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?", "list_confirm_delete": "මෙම ලැයිස්තුව ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාස ද?", - "auto_clients_desc": "ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන නිබැඳි අනුග්‍රාහක තුළ නැති උපාංග", - "access_title": "ප්‍රවේශවීමට සැකසුම්", - "access_desc": "මෙහිදී ඔබට ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකයට ප්‍රවේශ වී‌‌‌‌මේ නීති වින්‍යාසගත කළ හැකිය", - "access_allowed_title": "ඉඩ ලත් අනුග්‍රාහකයින්", - "access_allowed_desc": "අන.ජා.(CIDR), අ.ජා.කෙ. ලිපින හෝ අනුග්‍රාහක හැඳු. ලේඛනයකි. මෙහි නිවේශිත ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අනුග්‍රාහක වලින් පමණක් ඉල්ලීම් පිළිගනු ඇත.", - "access_disallowed_title": "නොඉඩ ලත් අනුග්‍රාහකයින්", - "access_disallowed_desc": "අන.ජා.(CIDR), අ.ජා.කෙ. ලිපින හෝ අනුග්‍රාහක හැඳු. ලේඛනයකි. මෙහි නිවේශිත ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අනුග්‍රාහක වලින් ඉල්ලීම් අත්හරිනු ඇත. ඉඩ ලත් අනුග්‍රාහකවල නිවේශිත තිබේ නම්, මෙම ක්‍ෂේත්‍රය නොසලකා හරිනු ඇත.", - "access_blocked_title": "නොඉඩ ලත් වසම්", + "auto_clients_desc": "ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන හෝ භාවිතයට ඉඩ තිබෙන උපාංගවල අ.ජා.කෙ. (IP) ලිපින පිළිබඳ තොරතුරු. මෙම තොරතුරු සත්කාරක ගොනු, ප්‍රතිවර්ත ව.නා.ප. ආදී මූලාශ්‍ර කිහිපයකින් රැස් කෙරේ.", + "access_title": "ප්‍රවේශ සැකසුම්", + "access_desc": "මෙහි දී ඔබට ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකයට ප්‍රවේශ වී‌‌‌‌මේ නීති වින්‍යාසගත කිරීමට හැකිය", + "access_allowed_title": "ඉඩ දෙන අනුග්‍රාහක", + "access_allowed_desc": "අන.ජා.(CIDR), අ.ජා.කෙ. ලිපින හෝ අනුග්‍රාහක හැඳු. ලේඛනයකි. මෙහි නිවේශිත ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් එම අනුග්‍රාහක වලින් පමණක් ඉල්ලීම් පිළිගනු ඇත.", + "access_disallowed_title": "ඉඩ නොදෙන අනුග්‍රාහක", + "access_disallowed_desc": "අන.ජා.(CIDR), අ.ජා.කෙ. ලිපින හෝ අනුග්‍රාහක හැඳු. ලේඛනයකි. මෙහි නිවේශිත ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් එම අනුග්‍රාහක වලින් ඉල්ලීම් අත්හරිනු ඇත. ඉඩ දෙන අනුග්‍රාහක කොටසේ නිවේශිත තිබේ නම්, මෙම ක්‍ෂේත්‍රය නොසලකා හරිනු ඇත.", + "access_blocked_title": "ඉඩ නොදෙන වසම්", "access_settings_saved": "ප්‍රවේශ වීමේ සැකසුම් සාර්ථකව සුරකින ලදි", "updates_checked": "ඇඩ්ගාර්ඩ් හෝම් හි නව අනුවාදයක් තිබේ", "updates_version_equal": "ඇඩ්ගාර්ඩ් හෝම් යාවත්කාලීනයි", @@ -461,7 +463,7 @@ "rewrite_confirm_delete": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?", "rewrite_desc": "නිශ්චිත වසම් නාමයක් සඳහා අභිරුචි ව.නා.ප. ප්‍රතිචාර පහසුවෙන් වින්‍යාසගත කිරීමට ඉඩ දෙයි.", "rewrite_applied": "නැවත ලිවීමේ නීතිය යොදා ඇත", - "rewrite_hosts_applied": "ධාරක ගොනු නීතිය මගින් නැවත ලියා ඇත", + "rewrite_hosts_applied": "සත්කාරක ගොනුවක නීතියකින් නැවත ලියා ඇත", "dns_rewrites": "ව.නා.ප. නැවත ලිවීම්", "form_answer": "අ.ජා.කෙ. (IP) ලිපිනය ‌හෝ වසම ඇතුල් කරන්න ", "form_error_domain_format": "වලංගු නොවන වසම් ආකෘතියකි", @@ -504,51 +506,53 @@ "statistics_enable": "සංඛ්‍යාලේඛන සබල කරන්න", "ignore_domains": "නොසලකන වසම් (පේළියකට එක බැගින්)", "ignore_domains_title": "නොසලකන වසම්", + "ignore_domains_desc_stats": "සංඛ්‍යාලේඛනයෙහි මෙම වසම් සඳහා විමසුම් නොලියැවෙයි", + "ignore_domains_desc_query": "විමසුම් සටහනෙහි මෙම වසම් සඳහා විමසුම් නොලියැවෙයි", "interval_hours": "පැය {{count}}", "interval_hours_plural": "පැය {{count}}", "filters_configuration": "පෙරහන් වින්‍යාසය", "filters_enable": "පෙරහන් සබල කරන්න", "filters_interval": "පෙරහන් යාවත්කාල කාල පරතරය", "disabled": "අබල කර ඇත", - "username_label": "පරිශීලක නාමය", - "username_placeholder": "පරිශීලක නාමය යොදන්න", + "username_label": "පරිශ්‍රීලක නාමය", + "username_placeholder": "පරිශ්‍රීලක නාමය යොදන්න", "password_label": "මුරපදය", "password_placeholder": "මුරපදය ඇතුල් කරන්න", "sign_in": "පුරන්න", "sign_out": "වරන්න", "forgot_password": "මුරපදය අමතක වුණා ද?", - "forgot_password_desc": "ඔබගේ පරිශීලක ගිණුම සඳහා නව මුරපදයක් සෑදීමට කරුණාකර <0>මෙම පියවර අනුගමනය කරන්න.", + "forgot_password_desc": "ඔබගේ පරිශ්‍රීලක ගිණුම සඳහා නව මුරපදයක් සෑදීමට කරුණාකර <0>මෙම පියවර අනුගමනය කරන්න.", "location": "ස්ථානය", "orgname": "සංවිධානයේ නම", "netname": "ජාල‌යේ නම", "network": "ජාලය", "descr": "සවිස්තරය", "whois": "Whois", - "filtering_rules_learn_more": "ඔබගේ ම ධාරක ලැයිස්තු සෑදීම පිළිබඳව <0>තව දැනගන්න.", + "filtering_rules_learn_more": "ඔබගේ ම සත්කාරක ලැයිස්තු සෑදීම පිළිබඳව <0>තව දැනගන්න.", "blocked_by_response": "ප්‍රතිචාරය අන්. නාමයක් (CNAME) හෝ අ.ජා.කෙ. මගින් අවහිර කර ඇත", "blocked_by_cname_or_ip": "අන්. නාමයක් (CNAME) හෝ අ.ජා.කෙ. මගින් අවහිර කර ඇත", "try_again": "යළි උත්සාහය", - "example_rewrite_domain": "මෙම වසම් නාමය සඳහා පමණක් ප්‍රතිචාර නැවත ලියන්න.", + "example_rewrite_domain": "මෙම වසම් නාමය සඳහා පමණක් ප්‍රතිචාර නැවත ලියයි.", "example_rewrite_wildcard": "<0>උදාහරණය.ලංකා සහ එහි සියළුම උප වසම් සඳහා ප්‍රතිචාර නැවත ලියයි.", - "rewrite_ip_address": "අ.ජා.කෙ. ලිපිනය: මෙම අ.ජා.කෙ. A හෝ AAAA ප්‍රතිචාරයකට ගන්න", - "rewrite_domain_name": "වසම් නාමය: අන්. නාම (CNAME) වාර්තාවක් එක්කරන්න", - "disable_ipv6": "අයිපීවී6 ලිපින විසඳීම අබල කරන්න", - "disable_ipv6_desc": "අ.ජා.කෙ. අනු.6 ලිපින (AAAA වර්ගය) සඳහා වන සියළුම ව.නා.ප. විමසුම් අතහැර දමයි.", - "fastest_addr": "වේගවත්ම අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය", - "fastest_addr_desc": "සියළුම ව.නා.ප. සේවාදායක වලින් විමසා සියළු ප්‍රතිචාර අතරින් වේගවත්ම අ.ජා.කෙ. ලිපිනය ලබා දෙයි. සියළුම ව.නා.ප. ප්‍රතිචාර සඳහා ඇඩ්ගාර්ඩ් හෝම් රැඳී සිටිය යුතු බැවින් මෙය ව.නා.ප. විමසුම් මන්දගාමී කරන නමුත් සමස්ත සම්බන්ධතාවය වැඩි දියුණු කරයි.", + "rewrite_ip_address": "අ.ජා.කෙ. ලිපිනය: A හෝ AAAA ප්‍රතිචාරයකට අ.ජා.කෙ. ලිපිනයක් යොදන්න", + "rewrite_domain_name": "වසම් නාමය: අන්වර්ථ නාමයක් (CNAME) එක් කරන්න", + "disable_ipv6": "IPv6 ලිපින විසඳීම අබල කරන්න", + "disable_ipv6_desc": "IPv6 ලිපින (AAAA වර්ගය) සඳහා වන සියළුම ව.නා.ප. විමසුම් අතහැර දමයි. HTTPS ප්‍රතිචාර වලින් IPv6 ඉඟි ඉවත් කරයි.", + "fastest_addr": "වේගවත්ම අන්තර්ජාල කෙටුම්පතක (IP) ලිපිනය", + "fastest_addr_desc": "සියළුම ව.නා.ප. සේවාදායක වලින් විමසා එම ප්‍රතිචාර අතරින් වේගවත්ම අ.ජා.කෙ. ලිපිනය ලබා දෙයි. සියළුම ව.නා.ප. ප්‍රතිචාර සඳහා ඇඩ්ගාර්ඩ් හෝම් රැඳී සිටිය යුතු බැවින් මෙය ව.නා.ප. විමසුම් මන්දගාමී කරන නමුත් සමස්ත සම්බන්ධතාවය වැඩි දියුණු කරයි.", "autofix_warning_text": "ඔබ \"නිරාකරණය\" යන්න එබුවහොත්, ඔබගේ පද්ධතිය ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය භාවිතයට වින්‍යාසගත කෙරෙනු ඇත.", "autofix_warning_result": "ප්‍රතිඵලයක් ලෙස ඔබගේ පද්ධතියෙන් ලැබෙන සියළුම ව.නා.ප. ඉල්ලීම් මූලිකවම ඇඩ්ගාර්ඩ් හෝම් විසින් සකසනු ඇත.", "tags_title": "අනන්‍යන", - "tags_desc": "අනුග්‍රාහකයට අනුරූපව අනන්‍යන ඔබට තෝරා ගත හැකිය. ඒවා වඩාත් නිවැරදිව යෙදීමට \nඅනන්‍යන පෙරහන් නීති වලට ඇතුළත් කරන්න. <0>තව දැන ගන්න.", + "tags_desc": "අනුග්‍රාහකයට අනුරූප වන අනන්‍යන ඔබට තේරීමට හැකිය. ඒවා වඩාත් නිවැරදිව යෙදීමට \nඅනන්‍යන පෙරහන් නීති වලට ඇතුළත් කරන්න. <0>තව දැන ගන්න.", "form_select_tags": "අනුග්‍රාහක අනන්‍යන තෝරන්න", "check_title": "පෙරීම පරීක්‍ෂා කරන්න", "check_desc": "සත්කාරක නාමය පෙරෙනවා දැයි පරීක්‍ෂා කරන්න.", "check": "පරීක්‍ෂාව", - "form_enter_host": "ධාරක නාමයක් ඇතුල් කරන්න", + "form_enter_host": "සත්කාරක නාමයක් යොදන්න", "filtered_custom_rules": "අභිරුචි පෙරීමේ නීති මගින් පෙරහන් කරන ලදි", "choose_from_list": "ලැයිස්තුවෙන් තෝරන්න", - "add_custom_list": "අභිරුචි ලැයිස්තුවක් එක්කරන්න", - "host_whitelisted": "ධාරකයට ඉඩ දී ඇත", + "add_custom_list": "අභිරුචි ලැයිස්තුවක්", + "host_whitelisted": "සත්කාරකයට ඉඩ දී ඇත", "check_ip": "අ.ජා.කෙ. ලිපින: {{ip}}", "check_cname": "අන්. නාමය (CNAME): {{cname}}", "check_reason": "හේතුව: {{reason}}", @@ -560,7 +564,7 @@ "client_blocked": "අනුග්‍රාහකය \"{{ip}}\" සාර්ථකව අවහිර කෙරිණි", "client_unblocked": "අනුග්‍රාහකය \"{{ip}}\" සාර්ථකව අනවහිර කෙරිණි", "static_ip": "ස්ථිතික අ.ජා. කෙ. ලිපිනය", - "static_ip_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු සේවාදායකයක් බැවින් එය නිසි ලෙස ක්‍රියා කිරීමට ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් ඇවැසිය. එසේ නැතිනම්, යම් අවස්ථාවක දී ඔබගේ මාර්ගකාරකය මෙම උපාංගයට වෙනත් අ.ජා. කෙ. ලිපිනයක් ලබා දිය හැකිය.", + "static_ip_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු සේවාදායකයක් බැවින් එය නිසි ලෙස ක්‍රියා කිරීමට ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් ඇවැසිය. එසේ නැතිනම්, යම් අවස්ථාවක දී ඔබගේ මාර්ගකාරකය මෙම උපාංගයට වෙනත් අ.ජා. කෙ. ලිපිනයක් ලබා දීමට ඉඩ තිබේ.", "set_static_ip": "ස්ථිතික අ.ජා.කෙ. (IP) ලිපිනයක් සකසන්න", "install_static_ok": "සුභ තොරතුරක්! ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය දැනටමත් වින්‍යාසගත කර ඇත", "install_static_error": "මෙම ජාල අතුරුමුහුණත සඳහා ඇඩ්ගාර්ඩ් හෝම් හට එය ස්වයංක්‍රීයව වින්‍යාසගත කිරීමට නොහැකිය. මෙය අතින් කරන්නේ කෙසේද යන්න පිළිබඳ උපදෙස් සොයා ගන්න.", @@ -593,7 +597,7 @@ "cache_ttl_max_override_desc": "ව.නා.ප. නිහිතයෙහි නිවේශිත සඳහා උපරිම පවත්නා කාලයක අගයක් (තත්.) සකසන්න.", "ttl_cache_validation": "නිහිතයෙහි පාගාගෙන යන අවම පව. කා. (TTL) උපරිමයට වඩා අඩු හෝ සමාන විය යුතුය", "cache_optimistic": "සර්වශුභවාදී නිහිතගතය", - "cache_optimistic_desc": "නිවේශිත කල් ඉකුත් වූ විට පවා ඇඩ්ගාර්ඩ් හෝම් ට නිහිතයෙන් ප්‍රතිචාර දැක්වීමට සලස්වයි එමෙන්ම ඒවා නැවත නැවුම් කිරීමට ද උත්සාහ කරයි.", + "cache_optimistic_desc": "නිවේශිත කල් ඉකුත් වූ විට පවා ඇඩ්ගාර්ඩ් හෝම් නිහිතයෙන් ප්‍රතිචාර දැක්වීමට සලස්වයි එමෙන්ම ඒවා නැවත නැවුම් කිරීමට ද උත්සාහ කරයි.", "filter_category_general": "පොදු", "filter_category_security": "ආරක්‍ෂණ", "filter_category_regional": "ප්‍රාදේශීය", @@ -641,6 +645,13 @@ "custom_rotation_input": "රඳවා ගැනීම පැය වලින්", "protection_section_label": "රැකවරණය", "log_and_stats_section_label": "විමසුම් සටහන හා සංඛ්‍යාලේඛන", - "ignore_query_log": "සටහනෙහි අනුග්‍රාහකය නොසලකන්න", - "ignore_statistics": "සංඛ්‍යාලේඛනයට අනුග්‍රාහකය නොසලකන්න" + "ignore_query_log": "විමසුම් සටහනට මෙම අනුග්‍රාහකය යොදන්න එපා", + "ignore_statistics": "සංඛ්‍යාලේඛනයට මෙම අනුග්‍රාහකය යොදන්න එපා", + "sunday_short": "ඉරිදා", + "monday_short": "සඳුදා", + "tuesday_short": "අඟහ", + "wednesday_short": "බදාදා", + "thursday_short": "බ්‍රහස්", + "friday_short": "සිකු", + "saturday_short": "සෙන" } diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json index 47e78933..9f167163 100644 --- a/client/src/__locales/sk.json +++ b/client/src/__locales/sk.json @@ -7,12 +7,15 @@ "load_balancing": "Vyrovnávanie záťaže", "load_balancing_desc": "Dopytovať len jeden server v danom čase. AdGuard Home použije na výber servera vážený náhodný algoritmus, aby sa najrýchlejší server používal častejšie.", "bootstrap_dns": "Bootstrap DNS servery", - "bootstrap_dns_desc": "Bootstrap DNS servery sa používajú na rozlíšenie IP adries DoH/DoT rezolverov, ktoré zadáte ako upstreams.", + "bootstrap_dns_desc": "IP adresy serverov DNS používaných na rozlíšenie IP adries prekladačov DoH/DoT, ktoré zadáte ako upstream. Komentáre nie sú povolené.", + "fallback_dns_title": "Záložné servery DNS", + "fallback_dns_desc": "Zoznam záložných serverov DNS, ktoré sa používajú, keď nadradený servery DNS neodpovedajú. Syntax je rovnaká ako v hlavnom poli vyššie.", + "fallback_dns_placeholder": "Zadajte jeden záložný server DNS na riadok", "local_ptr_title": "Súkromné reverzné DNS servery", "local_ptr_desc": "DNS servery, ktoré AdGuard Home používa pre miestne PTR dopyty. Tieto servery sa používajú na rozlíšenie názvov hostiteľov klientov so súkromnými adresami IP, napríklad \"192.168.12.34\", pomocou reverzného DNS. Ak nie je nastavené inak, AdGuard Home použije adresy predvolených prekladačov DNS Vášho operačného systému okrem adries samotného AdGuard Home.", "local_ptr_default_resolver": "V predvolenom nastavení používa AdGuard Home nasledujúce reverzné DNS prekladače: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home nemohol určiť vhodné súkromné reverzné DNS prekladače pre tento systém.", - "local_ptr_placeholder": "Na každý riadok zadajte adresu jedného servera", + "local_ptr_placeholder": "Na každý riadok zadajte IP adresu jedného servera", "resolve_clients_title": "Povoliť spätný preklad IP adries klientov", "resolve_clients_desc": "Reverzne rozlišuje adresy IP klientov na ich názvy hostiteľov odosielaním PTR dopytov príslušným prekladačom (súkromné DNS servery pre miestnych klientov, servery typu upstream pre klientov s verejnými IP adresami).", "use_private_ptr_resolvers_title": "Použiť súkromné reverzné DNS resolvery", @@ -125,6 +128,8 @@ "top_clients": "Najčastejší klienti", "no_clients_found": "Neboli nájdení žiadni klienti", "general_statistics": "Všeobecná štatistika", + "top_upstreams": "Často požadované upstream servery", + "no_upstreams_data_found": "Nenašli sa žiadne údaje o upstream serveroch", "number_of_dns_query_days": "Počet DNS dopytov spracovaných za posledný {{count}} deň", "number_of_dns_query_days_plural": "Počet DNS dopytov spracovaných za posledných {{count}} dní", "number_of_dns_query_24_hours": "Počet DNS dopytov spracovaných za posledných 24 hodín", @@ -134,6 +139,7 @@ "enforced_save_search": "Vynútené bezpečné vyhľadávanie", "number_of_dns_query_to_safe_search": "Počet DNS dopytov na vyhľadávače, pri ktorých bolo vynútené bezpečné vyhľadávanie", "average_processing_time": "Priemerný čas spracovania", + "processing_time": "Doba spracovania", "average_processing_time_hint": "Priemerný čas spracovania DNS dopytu v milisekundách", "block_domain_use_filters_and_hosts": "Blokovať domény pomocou filtrov a zoznamov adries", "filters_block_toggle_hint": "Pravidlá blokovania môžete nastaviť v nastaveniach Filtre.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Konfigurované v {{path}}", "test_upstream_btn": "Test upstreamov", "upstreams": "Upstreams", + "upstream": "Upstream server", "apply_btn": "Použiť", "disabled_filtering_toast": "Vypnutá filtrácia", "enabled_filtering_toast": "Zapnutá filtrácia", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: špeciálna hodnota, uchovávajte záznamy <0>A z upstream", "rewrite_AAAA": "<0>AAAA: špeciálna hodnota, uchovávajte záznamy <0>AAAA z upstream", "disable_ipv6": "Vypnúť rozlišovanie IPv6 adries", - "disable_ipv6_desc": "Vynechať všetky dotazy DNS na IPv6 adresy (typ AAAA).", + "disable_ipv6_desc": "Ignorovať všetky dotazy DNS na adresy IPv6 (typ AAAA) a odstrániť IPv6 údaje z HTTPS odpovedí.", "fastest_addr": "Najrýchlejšia IP adresa", "fastest_addr_desc": "Dopytovať všetky servery DNS a vrátiť najrýchlejšiu IP adresu zo všetkých odpovedí. Toto spomalí DNS dopyty, pretože AdGuard Home musí čakať na odpovede zo všetkých serverov DNS, ale zlepší sa celkové pripojenie.", "autofix_warning_text": "Ak kliknete na „Opraviť“, AdGuardHome nakonfiguruje Váš systém tak, aby používal DNS server AdGuardHome.", @@ -676,5 +683,37 @@ "protection_section_label": "Ochrana", "log_and_stats_section_label": "Protokol dopytov a štatistiky", "ignore_query_log": "Ignorovať tohto klienta v denníku dopytov", - "ignore_statistics": "Ignorovanie tohto klienta v štatistikách" + "ignore_statistics": "Ignorovanie tohto klienta v štatistikách", + "schedule_services": "Pozastavenie blokovania služby", + "schedule_services_desc": "Konfigurácia plánu pozastavenia filtra blokovania služieb", + "schedule_services_desc_client": "Konfigurácia plánu pozastavenia filtra blokovania služieb pre tohto klienta", + "schedule_desc": "Nastavenie doby nečinnosti pre blokované služby", + "schedule_invalid_select": "Čas začiatku musí byť pred časom ukončenia", + "schedule_select_days": "Zvoliť dni", + "schedule_timezone": "Vyberte časové pásmo", + "schedule_current_timezone": "Aktuálne časové pásmo: {{value}}", + "schedule_time_all_day": "Celý deň", + "schedule_modal_description": "Tento plán nahradí všetky existujúce plány na rovnaký deň v týždni. Každý deň v týždni môže mať iba jedno obdobie nečinnosti.", + "schedule_modal_time_off": "Žiadne blokovanie služby:", + "schedule_new": "Nový časový plán", + "schedule_edit": "Upraviť časový plán", + "schedule_save": "Uložiť časový plán", + "schedule_add": "Pridať časový plán", + "schedule_remove": "Odstrániť časový plán", + "schedule_from": "Od", + "schedule_to": "Do", + "sunday": "Nedeľa", + "monday": "Pondelok", + "tuesday": "Utorok", + "wednesday": "Streda", + "thursday": "Štvrtok", + "friday": "Piatok", + "saturday": "Sobota", + "sunday_short": "Ned", + "monday_short": "Pon", + "tuesday_short": "Uto", + "wednesday_short": "Str", + "thursday_short": "Štr", + "friday_short": "Pia", + "saturday_short": "Sob" } diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json index d1362923..72033550 100644 --- a/client/src/__locales/sl.json +++ b/client/src/__locales/sl.json @@ -7,12 +7,15 @@ "load_balancing": "Uravnavanje obremenitve", "load_balancing_desc": "Poizvedujte po enem strežniku navzgor. AdGuard Home s pomočjo tehtanega naključnega algoritma izbere strežnik, tako da se najpogosteje uporablja najhitrejši strežnik.", "bootstrap_dns": "Zagonski DNS strežniki", - "bootstrap_dns_desc": "Zagonski DNS strežniki se uporabljajo za razreševanje IP naslovov DoH/DoT reševalcev, ki jih določite kot navzgornje.", + "bootstrap_dns_desc": "Naslovi IP strežnikov DNS, ki se uporabljajo za razreševanje naslovov IP razreševalcev DoH/DoT, ki jih določite kot navzgor. Komentarji niso dovoljeni.", + "fallback_dns_title": "Rezervni strežniki DNS", + "fallback_dns_desc": "Seznam rezervnih strežnikov DNS, ki se uporabljajo, ko se gorvodni strežniki DNS ne odzivajo. Sintaksa je enaka kot v zgornjem gorvodnem polju.", + "fallback_dns_placeholder": "Vnesite en rezervni strežnik DNS na vrstico", "local_ptr_title": "Zasebni povratni strežniki DNS", "local_ptr_desc": "Strežniki DNS, ki jih AdGuard Home uporablja za lokalne PTR poizvedbe. Ti strežniki se uporabljajo za reševanje zahtev PTR za naslove v zasebnih obsegih IP, na primer \"192.168.12.34\", z uporabo obratnega DNS. Če ni nastavljen, AdGuard Home uporablja naslove privzetih razreševalnikov DNS vašega OS, razen naslovov samega AdGuard Home.", "local_ptr_default_resolver": "AdGuard Home privzeto uporablja te povratne razreševalnike DNS: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home ni mogel določiti ustreznih zasebnih povratnih reševalcev DNS za ta sistem.", - "local_ptr_placeholder": "V vrstico vnesite en naslov strežnika", + "local_ptr_placeholder": "Vnesite en naslov IP na vrstico", "resolve_clients_title": "Omogoči obratno reševanje naslovov IP gostiteljev", "resolve_clients_desc": "Povratno razrešite naslove IP odjemalcev v njihova gostiteljska imena, tako da pošljete poizvedbe PTR ustreznim razreševalcem (zasebni strežniki DNS za lokalne odjemalce, gorvodni strežniki za odjemalce z javnimi naslovi IP).", "use_private_ptr_resolvers_title": "Uporabi zasebne povratne razreševalnike rDNS", @@ -125,6 +128,8 @@ "top_clients": "Najpogostejši odjemalci", "no_clients_found": "Ni najdenih odjemalcev", "general_statistics": "Splošna statistika", + "top_upstreams": "Pogosto zahtevani gorvodni strežniki", + "no_upstreams_data_found": "Ni podatkov o gorvodnih strežnikih", "number_of_dns_query_days": "Število obdelanih poizvedb DNS v zadnjem {{count}} dnevu", "number_of_dns_query_days_plural": "Število obdelanih poizvedb DNS v zadnjih {{count}} dneh", "number_of_dns_query_24_hours": "Število obdelanih poizvedb DNS v zadnjih 24 urah", @@ -134,6 +139,7 @@ "enforced_save_search": "Prisilno varno iskanje", "number_of_dns_query_to_safe_search": "Število zahtev DNS za iskalnike, za katere je bilo uveljavljeno varno iskanje", "average_processing_time": "Povprečni čas obdelave", + "processing_time": "Čas obdelave", "average_processing_time_hint": "Povprečni čas v milisekundah pri obdelavi zahteve DNS", "block_domain_use_filters_and_hosts": "Onemogoči domene s filtri in gostiteljskimi datotekami", "filters_block_toggle_hint": "Pravila zaviranja lahko nastavite v nastavitvah Filtri.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Nastavljen v {{path}}", "test_upstream_btn": "Preizkusi upstreame", "upstreams": "Tokovi navzgor", + "upstream": "Gorvodni strežnik", "apply_btn": "Uporabi", "disabled_filtering_toast": "Onemogočeno filtriranje", "enabled_filtering_toast": "Omogočeno filtriranje", @@ -564,7 +571,7 @@ "rewrite_A": ">A: posebna vrednost, obdrži <0>A zapise iz gorvodnega toka", "rewrite_AAAA": ">A: posebna vrednost, obdrži <0>AAAA zapise iz gorvodnega toka", "disable_ipv6": "Onemogoči reševanje naslovov IPv6", - "disable_ipv6_desc": "Spusti vse poizvedbe DNS za naslove IPv6 (vnesite AAAA).", + "disable_ipv6_desc": "Spustite vse poizvedbe DNS za naslove IPv6 (tip AAAA) in odstranite namige IPv6 iz odgovorov HTTPS.", "fastest_addr": "Najhitrejši IP naslov", "fastest_addr_desc": "Zahtevajte vse strežnike DNS in vrne najhitrejši naslov IP med vsemi odgovori. To upočasni zahteve DNS, saj mora AdGuard Home čakati na odgovore vseh strežnikov DNS, vendar izboljša splošno povezljivost.", "autofix_warning_text": "Če kliknete 'Popravi', bo AdGuardHome konfiguriral vaš sistem za uporabo strežnika AdGuardHome DNS.", @@ -676,5 +683,37 @@ "protection_section_label": "Zaščita", "log_and_stats_section_label": "Dnevnik poizvedb in statistika", "ignore_query_log": "Ignorirajte tega odjemalca v dnevniku poizvedb", - "ignore_statistics": "Ignoriranje tega odjemalca v statistiki" + "ignore_statistics": "Ignoriranje tega odjemalca v statistiki", + "schedule_services": "Začasno ustavi onemogočanje storitve", + "schedule_services_desc": "Nastavite rokovnik premora filtra za onemogočanje storitev", + "schedule_services_desc_client": "Nastavite rokovnik premora filtra za onemogočanje storitev za tega odjemalca", + "schedule_desc": "Nastavite obdobja nedejavnosti onemogočenih storitev", + "schedule_invalid_select": "Začetni čas mora biti pred končnim časom", + "schedule_select_days": "Izberite dneve", + "schedule_timezone": "Izberite časovni pas", + "schedule_current_timezone": "Trenutni časovni pas: {{value}}", + "schedule_time_all_day": "Ves dan", + "schedule_modal_description": "Ta rokovnik bo nadomestil vse obstoječe rokovnike za isti dan v tednu. Vsak dan v tednu ima lahko samo eno obdobje neaktivnosti.", + "schedule_modal_time_off": "Brez onemogočanja storitve:", + "schedule_new": "Nov rokovnik", + "schedule_edit": "Uredi rokovnik", + "schedule_save": "Shrani rokovnik", + "schedule_add": "Dodaj rokovnik", + "schedule_remove": "Odstrani rokovnik", + "schedule_from": "Od", + "schedule_to": "Do", + "sunday": "Nedelja", + "monday": "Ponedeljek", + "tuesday": "Torek", + "wednesday": "Sreda", + "thursday": "Četrtek", + "friday": "Petek", + "saturday": "Sobota", + "sunday_short": "Ned", + "monday_short": "Pon", + "tuesday_short": "Tor", + "wednesday_short": "Sre", + "thursday_short": "Čet", + "friday_short": "Pet", + "saturday_short": "Sob" } diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json index f9e737ff..62b8dae1 100644 --- a/client/src/__locales/sr-cs.json +++ b/client/src/__locales/sr-cs.json @@ -7,12 +7,15 @@ "load_balancing": "Load-balancing", "load_balancing_desc": "Koristi jedan upstream server. AdGuard Home koristi najnoviji nasumični algoritam da izabere server tako da se najbrži server češće koristi.", "bootstrap_dns": "Bootstrap DNS serveri", - "bootstrap_dns_desc": "Bootstrap DNS serveri se koriste da reše IP adrese od DoH/DoT razrešivača koje ste odredili kao upstream.", + "bootstrap_dns_desc": "IP adrese DNS servera koje se koriste za rešavanje IP adresa DoH/DoT razrešivača koje navodite kao uzvodne. Komentari nisu dozvoljeni.", + "fallback_dns_title": "Odstupajući DNS serveri", + "fallback_dns_desc": "Lista povratnih DNS servera koji se koriste kada se uzvodni DNS serveri ne odaziva. Sintaksa je ista kao u glavnom uzvodnom polju iznad.", + "fallback_dns_placeholder": "Unesite jedan povratni DNS server po liniji", "local_ptr_title": "Private reverse DNS serveri", "local_ptr_desc": "DNS serveri koje AdGuard Home koristi za lokalne PTR upite. Ovi serveri se koriste za rešavanje imena domaćina klijenata sa privatnim IP adresama, na primer \"192.168.12.34\", koristeći obrnuti DNS. Ako nije podešen, AdGuard Home koristi adrese podrazumevanih DNS razrešivača vašeg OS-a osim adresa samog AdGuard Home-a.", "local_ptr_default_resolver": "Podrazumevano, AdGuard Home koristi sledeće obrnute DNS razrešivače: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home ne može da odredi pogodne privatne obrnute DNS razrešivače za ovaj sistem.", - "local_ptr_placeholder": "Unesite jednu adresu servera po redu", + "local_ptr_placeholder": "Unesite jednu IP adresu servera po redu", "resolve_clients_title": "Uključi obrnuto razrešavanje klijentskih IP adresa", "resolve_clients_desc": "Obrnuto razrešite IP adrese klijenata u njihova imena domaćina slanjem PTR upita odgovarajućim razrešivačima (privatni DNS serveri za lokalne klijente, uzvodni serveri za klijente sa javnim IP adresama).", "use_private_ptr_resolvers_title": "Koristi privatne obrnute razrešivače", @@ -125,6 +128,8 @@ "top_clients": "Najčešći klijenti", "no_clients_found": "Nema pronađenih klijenata", "general_statistics": "Opšte statistike", + "top_upstreams": "Često traženi upstream serveri", + "no_upstreams_data_found": "Nema podataka o upstream serverima", "number_of_dns_query_days": "Broj obrađenih DNS unosa u poslednjih {{count}} dan", "number_of_dns_query_days_plural": "Broj obrađenih DNS unosa u poslednjih {{count}} dana", "number_of_dns_query_24_hours": "Broj obrađenih DNS unosa u poslednja 24 časa", @@ -134,6 +139,7 @@ "enforced_save_search": "Nametni sigurno pretraživanje", "number_of_dns_query_to_safe_search": "Broj DNS zahteva ka pretraživačima za koje je nametnuto sigurno pretraživanje", "average_processing_time": "Prosečno vreme obrade", + "processing_time": "Vreme obrade", "average_processing_time_hint": "Prosečno vreme u milisekundama za obradu DNS zahteva", "block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtere i hosts datoteke", "filters_block_toggle_hint": "Možete postaviti pravila blokiranja u Filters postavkama.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Konfiguriši u {{path}}", "test_upstream_btn": "Testiraj upstreams", "upstreams": "Upstreams", + "upstream": "Upstream-server", "apply_btn": "Primeni", "disabled_filtering_toast": "Isključeno filtriranje", "enabled_filtering_toast": "Uključeno filtriranje", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: posebna vrednost, zadrži <0>A records iz apstrima", "rewrite_AAAA": "<0>AAAA: posebna vrednost, zadržite <0>AAAA records iz apstrima", "disable_ipv6": "Onemogući rešavanje IPv6 adresa", - "disable_ipv6_desc": "Otpustite sve DNS upite za IPv6 adrese (otkucajte AAAA).", + "disable_ipv6_desc": "Ignorisanje svih DNS upite za IPv6 adrese (tip AAAA) i uklanjanje IPv6 podataka iz HTTPS odgovora.", "fastest_addr": "Najbrža IP adresa", "fastest_addr_desc": "Pretražuje sve DNS servere i vraća najbržu IP adresu među svim odgovorima. Ovo će usporiti DNS pretragu jer moramo da čekamo na odgovore od svih DNS servera, ali će poboljšati sveukupnu povezanost.", "autofix_warning_text": "Ako kliknete \"Popravi\", AdGuardHome će konfigurisati vaš sistem da koristi AdGuardHome DNS server.", @@ -676,5 +683,37 @@ "protection_section_label": "Zaštita", "log_and_stats_section_label": "Evidencija upita i statistika", "ignore_query_log": "Zanemari ovog klijenta u evidenciji upita", - "ignore_statistics": "Zanemari ovog klijenta u statističkim podacima" + "ignore_statistics": "Zanemari ovog klijenta u statističkim podacima", + "schedule_services": "Pauziranje blokiranja usluge", + "schedule_services_desc": "Konfigurisanje rasporeda pauziranja filtera za blokiranje usluga", + "schedule_services_desc_client": "Konfigurišite raspored pauziranja filtera za blokiranje usluga za ovog klijenta", + "schedule_desc": "Podešavanje perioda neaktivnosti za blokirane usluge", + "schedule_invalid_select": "Vreme početka mora biti pre vremena završetka", + "schedule_select_days": "Izaberite dane", + "schedule_timezone": "Izaberite vremensku zonu", + "schedule_current_timezone": "Trenutna vremenska zona: {{value}}", + "schedule_time_all_day": "Ceo dan", + "schedule_modal_description": "Ovaj raspored će zameniti sve postojeće rasporede za isti dan u sedmici. Svaki dan u sedmici može imati samo jedan period neaktivnosti.", + "schedule_modal_time_off": "Nema blokiranja usluge:", + "schedule_new": "Novi raspored", + "schedule_edit": "Uredi raspored", + "schedule_save": "Sačuvaj raspored", + "schedule_add": "Dodaj raspored", + "schedule_remove": "Ukloni raspored", + "schedule_from": "Od", + "schedule_to": "Do", + "sunday": "Nedelja", + "monday": "Ponedeljak", + "tuesday": "Utorak", + "wednesday": "Sreda", + "thursday": "Četvrtak", + "friday": "Petak", + "saturday": "Subota", + "sunday_short": "Ned", + "monday_short": "Pon", + "tuesday_short": "Uto", + "wednesday_short": "Sre", + "thursday_short": "Čet", + "friday_short": "Pet", + "saturday_short": "Sub" } diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json index 1c02da65..2d3ee0b0 100644 --- a/client/src/__locales/sv.json +++ b/client/src/__locales/sv.json @@ -7,12 +7,15 @@ "load_balancing": "Lastbalansering", "load_balancing_desc": "Fråga en uppströmsserver åt gången. AdGuard Home använder sin viktade slumpmässiga algoritm för att välja server så att den snabbaste servern används oftare.", "bootstrap_dns": "Bootstrap-DNS-servrar", - "bootstrap_dns_desc": "Bootstrap-DNS-servrar används för att slå upp DoH/DoT-resolvrarnas IP-adresser som du specificerat som uppström.", + "bootstrap_dns_desc": "IP-adresser för DNS-servrar som används för att lösa IP-adresser för de DoH/DoT-resolvers som du anger som uppströms. Kommentarer är inte tillåtna.", + "fallback_dns_title": "Reserv-DNS-servrar", + "fallback_dns_desc": "Lista över reserv-DNS-servrar som används när uppströms DNS-servrar inte svarar. Syntaxen är densamma som i huvuduppströmsfältet ovan.", + "fallback_dns_placeholder": "Ange en reserv-DNS-server per rad", "local_ptr_title": "Privata omvända DNS-servrar", "local_ptr_desc": "DNS servrarna som AdGuard Home använder för lokala PTR frågor. Dessa servrar används för att lösa värdnamnen på klienter med privata IP-adresser, till exempel \"192.168.12.34\", genom omvänd DNS. Om inga servrar angetts använder AdGuard Home adresserna till standard DNS servrar för ditt operativsystem förutom adresserna till AdGuard Home själv.", "local_ptr_default_resolver": "Som standard använder AdGuard Home följande omvända DNS upplösare: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home kunde inte fastställa lämpliga privata omvända DNS upplösare för detta system.", - "local_ptr_placeholder": "Ange en serveradress per rad", + "local_ptr_placeholder": "Ange en IP-adress per rad", "resolve_clients_title": "Aktivera omvänd upplösning av klienters IP-adresser", "resolve_clients_desc": "Lös upp klienternas värdnamn med omvänt uppslag av klienternas IP-adresser genom att skicka PTR-frågor till motsvarande upplösare (privata DNS-servrar för lokala klienter, uppströmsservrar för klienter med offentliga IP-adresser).", "use_private_ptr_resolvers_title": "Använd privata omvända DNS upplösare", @@ -125,6 +128,8 @@ "top_clients": "Toppklienter", "no_clients_found": "Inga klienter hittade", "general_statistics": "Allmän statistik", + "top_upstreams": "Topp uppströmsservrar", + "no_upstreams_data_found": "Inga uppströmsdata hittades", "number_of_dns_query_days": "Antalet DNS-förfrågningar som utfördes under senaste {{count}} dagen", "number_of_dns_query_days_plural": "Ett antal DNS förfrågningar utfördes under de senaste {{count}} dagarna", "number_of_dns_query_24_hours": "Antalet DNS-förfrågningar som utfördes under de senaste 24 timmarna", @@ -134,6 +139,7 @@ "enforced_save_search": "Aktivering av Säker surf", "number_of_dns_query_to_safe_search": "Antalet DNS-förfrågningar mot sökmotorer där Säker surf tvingats", "average_processing_time": "Genomsnittlig processtid", + "processing_time": "Bearbetningstid", "average_processing_time_hint": "Genomsnittlig processtid i millisekunder för DNS-förfrågning", "block_domain_use_filters_and_hosts": "Blockera domäner med filter- och värdfiler", "filters_block_toggle_hint": "Du kan ställa in egna blockerings regler i Filterinställningar.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Konfigurerad i {{path}}", "test_upstream_btn": "Testa uppströmmar", "upstreams": "Uppströms", + "upstream": "Uppströms server", "apply_btn": "Tillämpa", "disabled_filtering_toast": "Filtrering bortkopplad", "enabled_filtering_toast": "Filtrering inkopplad", @@ -290,6 +297,8 @@ "rate_limit": "Förfrågnings gräns", "edns_enable": "Aktivera EDNS-klient subnät", "edns_cs_desc": "Skicka klienternas subnät till DNS servrarna.", + "edns_use_custom_ip": "Använd anpassad IP för EDNS", + "edns_use_custom_ip_desc": "Tillåt att använda anpassad IP för EDNS", "rate_limit_desc": "Antalet förfrågningar per sekund som tillåts per klient. Att sätta den till 0 innebär ingen gräns.", "blocking_ipv4_desc": "IP adress som ska returneras för en blockerad A förfrågan", "blocking_ipv6_desc": "IP adress som ska returneras för en blockerad AAAA förfrågan", @@ -441,7 +450,7 @@ "client_confirm_delete": "Är du säker på att du vill ta bort klient \"{{key}}\"?", "list_confirm_delete": "Är du säker på att du vill ta bort den här listan?", "auto_clients_title": "Klienter (körtid)", - "auto_clients_desc": "Enheter som inte finns på listan över beständiga klienter som fortfarande kan använda AdGuard Home", + "auto_clients_desc": "Information om IP-adresser för enheter som använder eller kan använda AdGuard Home. Denna information samlas in från flera källor, inklusive värdfiler, omvänd DNS, etc.", "access_title": "Åtkomstinställningar", "access_desc": "Här kan du konfigurera åtkomstregler för AdGuard Homes DNS-server", "access_allowed_title": "Tillåtna klienter", @@ -525,7 +534,10 @@ "statistics_retention_confirm": "Är du säker på att du vill ändra retentionstiden för statistik? Om du minskar intervallet kommer viss data att gå förlorad", "statistics_cleared": "Statistiken har rensats", "statistics_enable": "Aktivera statistik", + "ignore_domains": "Ignorerade domäner (avgränsade med ny rad)", "ignore_domains_title": "Ignorerade domäner", + "ignore_domains_desc_stats": "Förfrågningar för dessa domäner skrivs inte till statistiken", + "ignore_domains_desc_query": "Förfrågningar för dessa domäner skrivs inte till frågeloggningen", "interval_hours": "{{count}} timme", "interval_hours_plural": "{{count}} timmar", "filters_configuration": "Filterinställningar", @@ -558,7 +570,7 @@ "rewrite_A": "<0>A: specialvärde, behåll <0>A poster från uppströms", "rewrite_AAAA": "<0>AAAA: specialvärde, behåll <0>AAAA poster från uppströms", "disable_ipv6": "Inaktivera upplösning av IPv6 adresser", - "disable_ipv6_desc": "Kasta alla DNS-frågor för IPv6-adresser (typ AAAA).", + "disable_ipv6_desc": "Släpp alla DNS-frågor för IPv6-adresser (typ AAAA) och ta bort IPv6-tips från HTTPS-svar.", "fastest_addr": "Snabbaste IP adressen", "fastest_addr_desc": "Fråga alla DNS servrar och returnera den snabbaste IP adressen bland alla svar. Detta saktar ner DNS-frågor eftersom AdGuard Home måste vänta på svar från alla DNS servrar, men förbättrar den övergripande anslutningen.", "autofix_warning_text": "Om du klickar på \"Fix\" kommer AdGuard Home att konfigurera ditt system för att använda AdGuard Home DNS server.", @@ -646,6 +658,8 @@ "confirm_dns_cache_clear": "Är du säker på att du vill rensa DNS-cache?", "cache_cleared": "DNS-cacheminnet har rensats", "clear_cache": "Rensa cache", + "make_static": "Gör statisk", + "theme_auto_desc": "Auto (baserat på färgschemat på din enhet)", "theme_dark_desc": "Mörkt tema", "theme_light_desc": "Ljust tema", "disable_for_seconds": "I {{count}} sekund", @@ -654,5 +668,40 @@ "disable_for_minutes_plural": "I {{count}} minuter", "disable_for_hours": "I {{count}} timme", "disable_for_hours_plural": "I {{count}} timmar", - "disable_until_tomorrow": "Tills imorgon" + "disable_until_tomorrow": "Tills imorgon", + "protection_section_label": "Skydd", + "log_and_stats_section_label": "Förfrågningslogg och statistik", + "ignore_statistics": "Ignorera denna kund i statistiken", + "schedule_services": "Pausa blockering av tjänst", + "schedule_services_desc": "Konfigurera pausschemat för det tjänsteblockerande filtret", + "schedule_services_desc_client": "Konfigurera pausschemat för det tjänsteblockerande filtret för den här klienten", + "schedule_desc": "Ange inaktivitetsperioder för blockerade tjänster", + "schedule_invalid_select": "Starttid måste vara före sluttid", + "schedule_select_days": "Välj dagar", + "schedule_timezone": "Välj en tidszon", + "schedule_current_timezone": "Aktuell tidszon: {{value}}", + "schedule_time_all_day": "Hela dagen", + "schedule_modal_description": "Detta schema ersätter alla befintliga scheman för samma veckodag. Varje veckodag kan bara ha en inaktivitetsperiod.", + "schedule_modal_time_off": "Ingen blockering av tjänster:", + "schedule_new": "Nytt schema", + "schedule_edit": "Redigera schema", + "schedule_save": "Spara schema", + "schedule_add": "Lägg till schema", + "schedule_remove": "Ta bort schema", + "schedule_from": "Från", + "schedule_to": "Till", + "sunday": "Söndag", + "monday": "Måndag", + "tuesday": "Tisdag", + "wednesday": "Onsdag", + "thursday": "Torsdag", + "friday": "Fredag", + "saturday": "Lördag", + "sunday_short": "Sön", + "monday_short": "Mån", + "tuesday_short": "Tis", + "wednesday_short": "Ons", + "thursday_short": "Tor", + "friday_short": "Fre", + "saturday_short": "Lör" } diff --git a/client/src/__locales/th.json b/client/src/__locales/th.json index 9751530a..84a393f1 100644 --- a/client/src/__locales/th.json +++ b/client/src/__locales/th.json @@ -395,5 +395,12 @@ "safe_search": "ค้นหาอย่างปลอดภัย", "blocklist": "บัญชีดำ", "filter_category_other": "อื่น ๆ", - "parental_control": "ควบคุมโดยผู้ปกครอง" + "parental_control": "ควบคุมโดยผู้ปกครอง", + "sunday_short": "อาทิตย์", + "monday_short": "จันทร์", + "tuesday_short": "อังคาร", + "wednesday_short": "พุธ", + "thursday_short": "พฤหัส", + "friday_short": "ศุกร์", + "saturday_short": "เสาร์" } diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json index 3771f894..14114953 100644 --- a/client/src/__locales/tr.json +++ b/client/src/__locales/tr.json @@ -7,12 +7,15 @@ "load_balancing": "Yük dengeleme", "load_balancing_desc": "Her seferde bir üst sunucuyu sorgulayın. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.", "bootstrap_dns": "DNS Önyükleme sunucuları", - "bootstrap_dns_desc": "DNS Önyükleme sunucuları, belirttiğiniz üst sunucuların DoH/DoT çözümleyicilerine ait IP adreslerinin çözümlemek için kullanılır.", + "bootstrap_dns_desc": "Üst kaynak olarak belirttiğiniz DoH/DoT çözümleyicilerin IP adreslerini çözümlemek için kullanılan DNS sunucularının IP adresleri. Yorumlara izin verilmez.", + "fallback_dns_title": "Yedek DNS sunucuları", + "fallback_dns_desc": "Yukarı akış DNS sunucuları yanıt vermediğinde kullanılan yedek DNS sunucularının listesi. Söz dizimi yukarıdaki ana üst kaynak alanıyla aynıdır.", + "fallback_dns_placeholder": "Her satıra bir yedek DNS sunucusu girin", "local_ptr_title": "Özel ters DNS sunucuları", "local_ptr_desc": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, rDNS kullanarak, örneğin \"192.168.12.34\" gibi özel IP aralıklarındaki adresler için PTR isteklerini çözmek için kullanılır. Ayarlanmadığı durumda AdGuard Home, işletim sisteminizin varsayılan DNS çözümleme adreslerini kullanır.", "local_ptr_default_resolver": "AdGuard Home, varsayılan olarak aşağıdaki ters DNS çözümleyicilerini kullanır: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home, bu sistem için uygun olan özel ters DNS çözümleyicilerini belirleyemedi.", - "local_ptr_placeholder": "Her satıra bir sunucu adresi girin", + "local_ptr_placeholder": "Her satıra bir IP adresi girin", "resolve_clients_title": "İstemcilerin IP adreslerinin ters çözümlenmesini etkinleştir", "resolve_clients_desc": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunucuları) PTR sorguları göndererek istemcilerin IP adreslerini ana makine adlarının tersine çözün.", "use_private_ptr_resolvers_title": "Özel ters DNS çözümleyicileri kullan", @@ -125,6 +128,8 @@ "top_clients": "Başlıca istemciler", "no_clients_found": "İstemci bulunamadı", "general_statistics": "Genel istatistikler", + "top_upstreams": "Başlıca üst kaynaklar", + "no_upstreams_data_found": "Üst kaynak verisi bulunamadı", "number_of_dns_query_days": "Son {{count}} gün boyunca işlenen DNS sorgularının sayısı", "number_of_dns_query_days_plural": "Son {{count}} gün boyunca işlenen DNS sorgularının sayısı", "number_of_dns_query_24_hours": "Son 24 saat içinde işlenen DNS sorgularının sayısı", @@ -134,6 +139,7 @@ "enforced_save_search": "Uygulanan güvenli arama", "number_of_dns_query_to_safe_search": "Güvenli Aramanın uygulandığı arama motorlarına gönderilen DNS isteklerinin sayısı", "average_processing_time": "Ortalama işlem süresi", + "processing_time": "İşlem süresi", "average_processing_time_hint": "Bir DNS isteğinin milisaniye cinsinden ortalama işlem süresi", "block_domain_use_filters_and_hosts": "Filtre ve hosts dosyalarını kullanarak alan adlarını engelle", "filters_block_toggle_hint": "Filtreler ayarlarında engelleme kuralları oluşturabilirsiniz.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "{{path}} dosyasında yapılandırıldı", "test_upstream_btn": "Üst sunucuyu test et", "upstreams": "Üst kaynak", + "upstream": "Üst kaynak", "apply_btn": "Uygula", "disabled_filtering_toast": "Filtreleme devre dışı", "enabled_filtering_toast": "Filtreleme etkin", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: özel değer, üst sunucudan gelen <0>A kayıtlarını tutun", "rewrite_AAAA": "<0>AAA: özel değer, üst sunucudan gelen <0>AAA kayıtlarını tutun", "disable_ipv6": "IPv6 adreslerinin çözümlenmesini devre dışı bırak", - "disable_ipv6_desc": "IPv6 adresleri için tüm DNS sorgularını yanıtsız bırakır (AAAA yazar).", + "disable_ipv6_desc": "IPv6 adresleri için tüm DNS sorgularını bırakın (AAAA yazın) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırın.", "fastest_addr": "En hızlı IP adresi", "fastest_addr_desc": "Tüm DNS sunucularını sorgulayın ve tüm yanıtlar arasından en hızlı olan IP adresini döndürün. AdGuard Home'un tüm DNS sunucularından yanıt beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.", "autofix_warning_text": "\"Düzelt\" seçeneğine tıklarsanız, AdGuard Home, sisteminizi AdGuard Home DNS sunucusunu kullanacak şekilde yapılandırır.", @@ -676,5 +683,37 @@ "protection_section_label": "Koruma", "log_and_stats_section_label": "Sorgu günlüğü ve istatistikler", "ignore_query_log": "Sorgu günlüğünde bu istemciyi yoksay", - "ignore_statistics": "İstatistiklerde bu istemciyi yoksay" + "ignore_statistics": "İstatistiklerde bu istemciyi yoksay", + "schedule_services": "Hizmet engellemeyi duraklat", + "schedule_services_desc": "Hizmet engelleme filtresinin duraklatma planını yapılandırın", + "schedule_services_desc_client": "Bu istemci için hizmet engelleme filtresinin duraklatma planını yapılandırın", + "schedule_desc": "Engellenen hizmetler için hareketsizlik sürelerini ayarlayın", + "schedule_invalid_select": "Başlangıç zamanı, bitiş zamanından önce olmalıdır", + "schedule_select_days": "Günleri seçin", + "schedule_timezone": "Saat dilimi seçin", + "schedule_current_timezone": "Şu anki saat dilimi: {{value}}", + "schedule_time_all_day": "Tüm gün", + "schedule_modal_description": "Bu plan, haftanın aynı günü için mevcut planların yerini alır. Haftanın her gününde yalnızca bir hareketsizlik süresine sahip olabilir.", + "schedule_modal_time_off": "Hizmet engelleme yok:", + "schedule_new": "Yeni plan", + "schedule_edit": "Planı düzenle", + "schedule_save": "Planı kaydet", + "schedule_add": "Plan ekle", + "schedule_remove": "Planı kaldır", + "schedule_from": "Başlangıç", + "schedule_to": "Bitiş", + "sunday": "Pazar", + "monday": "Pazartesi", + "tuesday": "Salı", + "wednesday": "Çarşamba", + "thursday": "Perşembe", + "friday": "Cuma", + "saturday": "Cumartesi", + "sunday_short": "Paz", + "monday_short": "Pzt", + "tuesday_short": "Sal", + "wednesday_short": "Çar", + "thursday_short": "Per", + "friday_short": "Cum", + "saturday_short": "Cmt" } diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json index 8cb111d0..f997077e 100644 --- a/client/src/__locales/uk.json +++ b/client/src/__locales/uk.json @@ -7,7 +7,10 @@ "load_balancing": "Балансування навантаження", "load_balancing_desc": "Запитувати один сервер за раз. AdGuard Home використовуватиме зважений випадковий алгоритм для вибору сервера, щоб найшвидший сервер використовувався частіше.", "bootstrap_dns": "Bootstrap DNS-сервери", - "bootstrap_dns_desc": "Bootstrap DNS-сервери використовуються для вирішення IP-адрес встановлених серверів DoH/DoT.", + "bootstrap_dns_desc": "IP-адреси DNS-серверів, які використовуються для визначення IP-адрес DoH/DoT-розпізнавачів, які ви вказуєте як висхідні. Коментарі заборонені.", + "fallback_dns_title": "Резервні DNS-сервери", + "fallback_dns_desc": "Список резервних DNS-серверів, які використовуються, коли upstream DNS-сервери не відповідають. Синтаксис такий самий, як і в полі upstream сервера вище.", + "fallback_dns_placeholder": "Вводьте один резервний DNS-сервер на рядок", "local_ptr_title": "Приватні сервери для зворотного DNS", "local_ptr_desc": "DNS-сервери, які AdGuard Home використовує для локальних PTR-запитів. Ці сервери використовуються для вирішення PTR-запитів для адрес у приватних мережах, наприклад, «192.168.12.34». Якщо список порожній, AdGuard Home буде усталено використовувати системний DNS-сервер.", "local_ptr_default_resolver": "Стандартно AdGuard Home користується такими зворотними DNS-вирішувачами: {{ip}}.", @@ -125,6 +128,8 @@ "top_clients": "Найактивніші клієнти", "no_clients_found": "Клієнтів не знайдено", "general_statistics": "Загальна статистика", + "top_upstreams": "Часто запитувані upstream-сервери", + "no_upstreams_data_found": "Немає даних про upstream-сервери", "number_of_dns_query_days": "Кількість DNS-запитів, оброблених за останні {{count}} дні", "number_of_dns_query_days_plural": "Кількість DNS-запитів, оброблених за останні {{count}} днів", "number_of_dns_query_24_hours": "Кількість DNS-запитів, оброблених за останні 24 години", @@ -134,6 +139,7 @@ "enforced_save_search": "Примусовий безпечний пошук", "number_of_dns_query_to_safe_search": "Кількість DNS-запитів до пошукових систем, для яких примусово застосований безпечний пошук", "average_processing_time": "Середній час обробки", + "processing_time": "Час обробки", "average_processing_time_hint": "Середній час обробки DNS запиту в мілісекундах", "block_domain_use_filters_and_hosts": "Блокування доменів за допомогою фільтрів та hosts-файлів", "filters_block_toggle_hint": "Ви можете налаштувати правила блокування в розділі Фільтри.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Налаштовано в {{path}}", "test_upstream_btn": "Перевірити сервери", "upstreams": "Upstreams", + "upstream": "Upstream-сервер", "apply_btn": "Застосувати", "disabled_filtering_toast": "Фільтрування вимкнено", "enabled_filtering_toast": "Фільтрування увімкнено", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: спеціальне значення, зберігайте <0>A записи із вищого сервера", "rewrite_AAAA": "<0>AAAA: спеціальне значення, зберігайте <0>AAAA записи із вищого сервера", "disable_ipv6": "Вимкнути вирішення IPv6-адрес", - "disable_ipv6_desc": "Ігнорувати DNS-запити для IPv6-адрес (тип AAAA).", + "disable_ipv6_desc": "Ігнорувати всі DNS-запити адрес IPv6 (тип AAAA) та видаляти IPv6-дані з відповідей типу HTTPS.", "fastest_addr": "Найшвидша IP-адреса", "fastest_addr_desc": "Опитати всі DNS-сервери й повернути найшвидшу IP-адресу серед усіх наданих. Це сповільнить швидкість DNS-запитів, оскільки AdGuard Home повинен буде чекати відповіді усіх DNS-серверів, але водночас може покращити якість з'єднання.", "autofix_warning_text": "Якщо ви натиснете «Виправити», AdGuard Home налаштує вашу систему на використання DNS-сервера AdGuard Home.", @@ -676,5 +683,37 @@ "protection_section_label": "Захист", "log_and_stats_section_label": "Журнал запитів і статистика", "ignore_query_log": "Ігнорувати цей клієнт у журналі запитів", - "ignore_statistics": "Ігноруйте цей клієнт в статистиці" + "ignore_statistics": "Ігноруйте цей клієнт в статистиці", + "schedule_services": "Пауза блокування сервісів", + "schedule_services_desc": "Налаштування розкладу паузи фільтра блокування сервісів", + "schedule_services_desc_client": "Налаштування розкладу паузи фільтра блокування сервісів для даного клієнта", + "schedule_desc": "Установка періодів паузи блокування сервісів", + "schedule_invalid_select": "Час початку має бути завчасно закінчення", + "schedule_select_days": "Вибрати дні", + "schedule_timezone": "Вибрати часовий пояс", + "schedule_current_timezone": "Поточний часовий пояс: {{value}}", + "schedule_time_all_day": "Увесь день", + "schedule_modal_description": "Цей розклад замінить усі наявні розклади на той самий день тижня. Кожен день тижня може мати тільки один період бездіяльності.", + "schedule_modal_time_off": "Блокування сервісів відключена:", + "schedule_new": "Новий розклад", + "schedule_edit": "Редагувати розклад", + "schedule_save": "Зберегти розклад", + "schedule_add": "Додати розклад", + "schedule_remove": "Видалити розклад", + "schedule_from": "З", + "schedule_to": "До", + "sunday": "Неділя", + "monday": "Понеділок", + "tuesday": "Вівторок", + "wednesday": "Середа", + "thursday": "Четвер", + "friday": "П'ятниця", + "saturday": "Субота", + "sunday_short": "НД", + "monday_short": "ПН", + "tuesday_short": "ВТ", + "wednesday_short": "СР", + "thursday_short": "ЧТ", + "friday_short": "ПТ", + "saturday_short": "СБ" } diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json index 52ffccec..bbc81890 100644 --- a/client/src/__locales/vi.json +++ b/client/src/__locales/vi.json @@ -7,12 +7,15 @@ "load_balancing": "Cân bằng tải", "load_balancing_desc": "Chỉ truy xuất một máy chủ trong cùng thời điểm. AdGuard Home sẽ sử dụng thuật toán trọng số ngẫu nhiên để chọn một máy chủ nhanh nhất và sử dụng máy chủ đó thường xuyên hơn.", "bootstrap_dns": "Máy chủ DNS Bootstrap", - "bootstrap_dns_desc": "Máy chủ DNS Bootstrap được sử dụng để phân giải địa chỉ IP của bộ phân giải DoH/DoT mà bạn chỉ định là ngược tuyến.", + "bootstrap_dns_desc": "Địa chỉ IP của máy chủ DNS được sử dụng để phân giải địa chỉ IP của trình phân giải DoH/DoT mà bạn chỉ định làm thượng nguồn. Bình luận không được phép.", + "fallback_dns_title": "Máy chủ DNS dự phòng", + "fallback_dns_desc": "Danh sách máy chủ DNS dự phòng được sử dụng khi máy chủ DNS ngược tuyến không phản hồi. Cú pháp tương tự như trong trường ngược dòng chính ở trên.", + "fallback_dns_placeholder": "Nhập một máy chủ DNS dự phòng trên mỗi dòng", "local_ptr_title": "Máy chủ DNS riêng tư", "local_ptr_desc": "Máy chủ DNS hoặc các máy chủ mà AdGuard Home sẽ sử dụng cho các truy vấn về tài nguyên được phân phối cục bộ. Ví dụ: máy chủ này sẽ được sử dụng để phân giải tên máy khách của máy khách cho các máy khách có địa chỉ IP riêng. Nếu không được cài đặt, AdGuard Home sẽ tự động sử dụng trình phân giải DNS mặc định của bạn.", "local_ptr_default_resolver": "Theo mặc định, AdGuard Home sử dụng các hệ thống phân giải tên miền ngược sau: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home không thể xác định hệ thống phân giải tên miền ngược riêng phù hợp cho hệ thống này.", - "local_ptr_placeholder": "Nhập một địa chỉ máy chủ trên mỗi dòng", + "local_ptr_placeholder": "Nhập một địa chỉ IP trên mỗi dòng", "resolve_clients_title": "Kích hoạt cho phép phân giải ngược về địa chỉ IP của máy khách", "resolve_clients_desc": "Nếu được bật, AdGuard Home sẽ cố gắng phân giải ngược lại địa chỉ IP của khách hàng thành tên máy chủ của họ bằng cách gửi các truy vấn PTR tới trình phân giải tương ứng (máy chủ DNS riêng cho máy khách cục bộ, máy chủ ngược dòng cho máy khách có địa chỉ IP công cộng).", "use_private_ptr_resolvers_title": "Sử dụng trình rDNS riêng tư", @@ -125,6 +128,8 @@ "top_clients": "Người dùng hàng đầu", "no_clients_found": "Không có người dùng", "general_statistics": "Thống kê chung", + "top_upstreams": "Máy chủ thượng nguồn hàng đầu", + "no_upstreams_data_found": "Không tìm thấy dữ liệu máy chủ ngược dòng", "number_of_dns_query_days": "Một số truy vấn DNS được xử lý trong {{count}} ngày qua", "number_of_dns_query_days_plural": "Một số truy vấn DNS được xử lý trong {{count}} ngày qua", "number_of_dns_query_24_hours": "Số yêu cầu DNS đã xử lý trong 24 giờ qua", @@ -134,6 +139,7 @@ "enforced_save_search": "Bắt buộc tìm kiếm an toàn", "number_of_dns_query_to_safe_search": "Số yêu cầu DNS tới công cụ tìm kiếm đã chuyển thành tìm kiếm an toàn", "average_processing_time": "Thời gian xử lý trung bình", + "processing_time": "Thời gian xử lý", "average_processing_time_hint": "Thời gian trung bình cho một yêu cầu DNS tính bằng mili giây", "block_domain_use_filters_and_hosts": "Chặn tên miền sử dụng các bộ lọc và file hosts", "filters_block_toggle_hint": "Bạn có thể thiết lập quy tắc chặn tại cài đặt Bộ lọc.", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "Cấu hình tại {{path}}", "test_upstream_btn": "Kiểm tra", "upstreams": "Nguồn", + "upstream": "Máy chủ thượng nguồn", "apply_btn": "Áp dụng", "disabled_filtering_toast": "Đã tắt chặn quảng cáo", "enabled_filtering_toast": "Đã bật chặn quảng cáo", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A: giá trị đặc biệt, giữ bản ghi <0>A từ nguồn", "rewrite_AAAA": "<0>A: giá trị đặc biệt, giữ bản ghi <0>A từ nguồn", "disable_ipv6": "Tắt IPv6", - "disable_ipv6_desc": "Nếu tính năng này được bật, tất cả các truy vấn DNS cho địa chỉ IPv6 (loại AAAA) sẽ bị loại bỏ.", + "disable_ipv6_desc": "Bỏ tất cả truy vấn DNS cho địa chỉ IPv6 (loại AAAA) và xóa gợi ý IPv6 khỏi phản hồi HTTPS.", "fastest_addr": "Địa chỉ IP nhanh nhất", "fastest_addr_desc": "Truy vấn tất cả các máy chủ DNS và trả về địa chỉ IP nhanh nhất trong số tất cả các phản hồi", "autofix_warning_text": "Nếu bạn nhấp vào \"Khắc phục\", AdGuard Home sẽ định cấu hình hệ thống của bạn để sử dụng máy chủ DNS của AdGuard Home.", @@ -676,5 +683,37 @@ "protection_section_label": "Sự bảo vệ", "log_and_stats_section_label": "Nhật ký truy vấn và thống kê", "ignore_query_log": "Bỏ qua máy khách này trong nhật ký truy vấn", - "ignore_statistics": "Bỏ qua máy khách này trong thống kê" + "ignore_statistics": "Bỏ qua máy khách này trong thống kê", + "schedule_services": "Tạm dừng chặn dịch vụ", + "schedule_services_desc": "Định cấu hình lịch tạm dừng của bộ lọc chặn dịch vụ", + "schedule_services_desc_client": "Định cấu hình lịch tạm dừng của bộ lọc chặn dịch vụ cho máy khách này", + "schedule_desc": "Đặt khoảng thời gian không hoạt động cho các dịch vụ bị chặn", + "schedule_invalid_select": "Thời gian bắt đầu phải trước thời gian kết thúc", + "schedule_select_days": "Chọn ngày", + "schedule_timezone": "Chọn múi giờ", + "schedule_current_timezone": "Múi giờ hiện tại: {{value}}", + "schedule_time_all_day": "Cả ngày", + "schedule_modal_description": "Lịch trình này sẽ thay thế mọi lịch trình hiện có cho cùng một ngày trong tuần. Mỗi ngày trong tuần chỉ có thể có một khoảng thời gian không hoạt động.", + "schedule_modal_time_off": "Chặn dịch vụ bị vô hiệu hóa:", + "schedule_new": "Kế hoạch mới", + "schedule_edit": "Chỉnh sửa lịch trình", + "schedule_save": "Lưu lịch trình", + "schedule_add": "Thêm lịch trình", + "schedule_remove": "Xóa lịch biểu", + "schedule_from": "Từ", + "schedule_to": "Đến", + "sunday": "Chủ nhật", + "monday": "Thứ hai", + "tuesday": "Thứ ba", + "wednesday": "Thứ Tư", + "thursday": "Thứ năm", + "friday": "Thứ sáu", + "saturday": "Thứ bảy", + "sunday_short": "Chủ nhật", + "monday_short": "Thứ 2", + "tuesday_short": "Thứ 3", + "wednesday_short": "Thứ 4", + "thursday_short": "Thứ 5", + "friday_short": "Thứ 6", + "saturday_short": "Thứ 7" } diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json index baa98d37..4c618a26 100644 --- a/client/src/__locales/zh-cn.json +++ b/client/src/__locales/zh-cn.json @@ -7,12 +7,15 @@ "load_balancing": "负载均衡", "load_balancing_desc": "一次查询一台服务器。AdGuard Home 将使用加权随机算法来选择服务器,以便更常使用最快的服务器。", "bootstrap_dns": "Bootstrap DNS 服务器", - "bootstrap_dns_desc": "Bootstrap DNS 服务器用于解析您指定为上游的 DoH / DoT 解析器的 IP 地址。", + "bootstrap_dns_desc": "DNS 服务器的 IP 地址,用于解析指定为上游的 DoH/DoT 解析器的 IP 地址。不允许评论。", + "fallback_dns_title": "后备 DNS 服务器", + "fallback_dns_desc": "当上游 DNS 服务器没有响应时使用的后备 DNS 服务器列表。语法与上面的主要上游字段相同。", + "fallback_dns_placeholder": "每行输入一个后备 DNS 服务器", "local_ptr_title": "私人反向 DNS 服务器", "local_ptr_desc": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器。这些服务器将使用反向 DNS 解析具有私人 IP 地址的客户机的主机名,比如 \"192.168.12.34\"。如果没有设置,除非是 AdGuard Home 里设置的地址,AdGuard Home 都将自动使用您的操作系统的默认 DNS 解析器。", "local_ptr_default_resolver": "AdGuard Home 默认使用下列反向 DNS 解析器: {{ip}}", "local_ptr_no_default_resolver": "AdGuard Home 无法为这个系统确定合适的私人反向 DNS 解析器。", - "local_ptr_placeholder": "每行输入一个服务器地址", + "local_ptr_placeholder": "每行输入一个 IP 地址", "resolve_clients_title": "启用客户端的 IP 地址的反向解析", "resolve_clients_desc": "通过发送 PTR 查询到对应的解析器 (本地客户端的私人 DNS 服务器,公共 IP 客户端的上游服务器) 将 IP 地址反向解析成其客户端主机名。", "use_private_ptr_resolvers_title": "使用私人反向 DNS 解析器", @@ -125,6 +128,8 @@ "top_clients": "客户端排行", "no_clients_found": "未找到客户端", "general_statistics": "概况统计", + "top_upstreams": "经常请求的上游服务器", + "no_upstreams_data_found": "未找到上游服务器数据", "number_of_dns_query_days": "过去 {{count}} 天内处理的 DNS 查询总数", "number_of_dns_query_days_plural": "在过去的 {{count}} 天内处理了多少个 DNS 查询", "number_of_dns_query_24_hours": "过去 24 小时内处理的 DNS 请求总数", @@ -134,6 +139,7 @@ "enforced_save_search": "强制安全搜索", "number_of_dns_query_to_safe_search": "启用强制安全搜索后对搜索引擎的 DNS 请求总数", "average_processing_time": "平均处理时间", + "processing_time": "处理时间", "average_processing_time_hint": "处理 DNS 请求的平均时间(毫秒)", "block_domain_use_filters_and_hosts": "使用过滤器和 Hosts 文件以拦截指定域名", "filters_block_toggle_hint": "你可以在 过滤器 设置中添加过滤规则。", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "配置路径{{path}}", "test_upstream_btn": "测试上游 DNS", "upstreams": "上游服务器", + "upstream": "上游服务器", "apply_btn": "应用", "disabled_filtering_toast": "过滤器已禁用", "enabled_filtering_toast": "过滤器已启用", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A:特殊值,保持来自上游的<0>A记录", "rewrite_AAAA": "<0>AAAA:特殊值,保持来自上游的<0>AAAA记录", "disable_ipv6": "禁用 IPv6 地址的解析", - "disable_ipv6_desc": "丢弃所有 IPv6 地址 (AAAA) 的 DNS 查询。", + "disable_ipv6_desc": "删除对 IPv6 地址(类型 AAAA)的所有 DNS 查询,并从 HTTPS 响应中删除 IPv6 提示。", "fastest_addr": "最快的 IP 地址", "fastest_addr_desc": "查询所有 DNS 服务器并返回所有响应中速度最快的 IP 地址。因 AdGuard Home 必须等待全部 DNS 服务器均有所回应,因而会降低 DNS 查询的速度,但同时,此举将会改善总体的连接。", "autofix_warning_text": "若您单击「修复」,AdGuardHome 将会配置您的系统以使用 AdGuardHome 的 DNS 服务器。", @@ -676,5 +683,37 @@ "protection_section_label": "防护", "log_and_stats_section_label": "查询日志和统计数据", "ignore_query_log": "在查询日志中忽略此客户端", - "ignore_statistics": "在统计数据中忽略此客户端" + "ignore_statistics": "在统计数据中忽略此客户端", + "schedule_services": "暂停服务拦截", + "schedule_services_desc": "配置服务拦截过滤器的暂停计划", + "schedule_services_desc_client": "为此客户端配置服务拦截过滤器的暂停计划", + "schedule_desc": "安排已拦截服务禁用的时间", + "schedule_invalid_select": "开始时间必须在结束时间之前", + "schedule_select_days": "选择日", + "schedule_timezone": "选择时区", + "schedule_current_timezone": "当前时区:{{value}}", + "schedule_time_all_day": "全天", + "schedule_modal_description": "本时间表将要替换现有同一天内所有时间表。一个星期内只可以有一段不活跃期。", + "schedule_modal_time_off": "无服务拦截:", + "schedule_new": "新时间表", + "schedule_edit": "编辑时间表", + "schedule_save": "保存时间表", + "schedule_add": "添加时间表", + "schedule_remove": "移除时间表", + "schedule_from": "从", + "schedule_to": "到", + "sunday": "星期日", + "monday": "星期一", + "tuesday": "星期二", + "wednesday": "星期三", + "thursday": "星期四", + "friday": "星期五", + "saturday": "星期六", + "sunday_short": "周日", + "monday_short": "周一", + "tuesday_short": "周二", + "wednesday_short": "周三", + "thursday_short": "周四", + "friday_short": "周五", + "saturday_short": "周六" } diff --git a/client/src/__locales/zh-hk.json b/client/src/__locales/zh-hk.json index 48d45a5c..ea2d792b 100644 --- a/client/src/__locales/zh-hk.json +++ b/client/src/__locales/zh-hk.json @@ -648,5 +648,12 @@ "disable_notify_for_seconds_plural": "暫停防護 {{count}} 秒", "disable_notify_for_minutes": "暫停防護 {{count}} 分鐘", "disable_notify_for_minutes_plural": "暫停防護 {{count}} 分鐘", - "disable_notify_for_hours": "暫停防護 {{count}} 小時" + "disable_notify_for_hours": "暫停防護 {{count}} 小時", + "sunday_short": "週日", + "monday_short": "週一", + "tuesday_short": "週二", + "wednesday_short": "週三", + "thursday_short": "週四", + "friday_short": "週五", + "saturday_short": "週六" } diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json index 0d6419e1..2b571f3c 100644 --- a/client/src/__locales/zh-tw.json +++ b/client/src/__locales/zh-tw.json @@ -7,12 +7,15 @@ "load_balancing": "負載平衡", "load_balancing_desc": "每次查詢一個上游伺服器。AdGuard Home 使用它的加權隨機的演算法來選擇伺服器,以便最快的伺服器被更常使用。", "bootstrap_dns": "自我啟動(Bootstrap)DNS 伺服器", - "bootstrap_dns_desc": "自我啟動(Bootstrap)DNS 伺服器被用於解析您明確指定作為上游的 DoH/DoT 解析器之 IP 位址。", + "bootstrap_dns_desc": "DNS 伺服器的 IP 位址,用於解析您指定為上游伺服器的 DoH/DoT 解析器的 IP 位址。不允許註釋。", + "fallback_dns_title": "備援 DNS 伺服器", + "fallback_dns_desc": "當上游 DNS 伺服器未回應時使用的備援 DNS 伺服器清單。語法與上面的主要上游欄位相同。", + "fallback_dns_placeholder": "每行輸入一個備援 DNS 伺服器", "local_ptr_title": "私人反向的 DNS 伺服器", "local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析有關在私人 IP 範圍的位址之區域指標查詢,例如,\"192.168.12.34\",使用反向的 DNS。如果未被設定,AdGuard Home 使用您的作業系統之預設 DNS 解析器的位址。", "local_ptr_default_resolver": "預設下,AdGuard Home 使用以下反向的 DNS 解析器:{{ip}}。", "local_ptr_no_default_resolver": "AdGuard Home 無法為此系統決定合適的私人反向的 DNS 解析器。", - "local_ptr_placeholder": "每行輸入一個伺服器位址", + "local_ptr_placeholder": "每行輸入一個 IP 位址", "resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析", "resolve_clients_desc": "透過傳送指標(PTR)查詢到對應的解析器(私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。", "use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器", @@ -125,6 +128,8 @@ "top_clients": "熱門用戶端", "no_clients_found": "無已發現之用戶端", "general_statistics": "一般的統計資料", + "top_upstreams": "經常請求的上游伺服器", + "no_upstreams_data_found": "找不到上游伺服器資料", "number_of_dns_query_days": "在最近的 {{count}} 日內已處理的 DNS 查詢之數量", "number_of_dns_query_days_plural": "在最近的 {{count}} 日內已處理的 DNS 查詢之數量", "number_of_dns_query_24_hours": "在最近的 24 小時內已處理的 DNS 查詢之數量", @@ -134,6 +139,7 @@ "enforced_save_search": "已強制執行的安全搜尋", "number_of_dns_query_to_safe_search": "安全搜尋已被強制執行之屬於搜尋引擎的 DNS 請求之數量", "average_processing_time": "平均的處理時間", + "processing_time": "處理時間", "average_processing_time_hint": "在處理一項 DNS 請求時以毫秒(ms)計的平均時間", "block_domain_use_filters_and_hosts": "透過過濾器和主機檔案封鎖網域", "filters_block_toggle_hint": "您可在過濾器設定中設置封鎖規則。", @@ -158,6 +164,7 @@ "upstream_dns_configured_in_file": "被配置在 {{path}}", "test_upstream_btn": "測試上行資料流", "upstreams": "上游", + "upstream": "上游伺服器", "apply_btn": "套用", "disabled_filtering_toast": "已禁用過濾", "enabled_filtering_toast": "已啟用過濾", @@ -564,7 +571,7 @@ "rewrite_A": "<0>A:特殊的數值,阻止 <0>A 記錄免於該上游", "rewrite_AAAA": "<0>AAAA:特殊的數值,阻止 <0>AAAA 記錄免於該上游", "disable_ipv6": "禁用 IPv6 位址之解析", - "disable_ipv6_desc": "丟棄所有對於 IPv6 位址(類型 AAAA)的 DNS 查詢。", + "disable_ipv6_desc": "停止所有對於 IPv6 位址(類型 AAAA)的 DNS 查詢,並從 HTTPS 回應中移除 IPv6 的提示。", "fastest_addr": "最快的 IP 位址", "fastest_addr_desc": "查詢所有的 DNS 伺服器並返回在所有的回應之中最快的 IP 位址。因為 AdGuard Home 必須等待來自所有的 DNS 伺服器之回應,這使 DNS 查詢變慢,但改善總體的連線。", "autofix_warning_text": "如果您點擊\"修復\",AdGuard Home 將配置您的系統使用 AdGuard Home DNS 伺服器。", @@ -676,5 +683,37 @@ "protection_section_label": "防護", "log_and_stats_section_label": "查詢記錄和統計資料", "ignore_query_log": "在查詢記錄中忽略此用戶端", - "ignore_statistics": "在統計資料中忽略此用戶端" + "ignore_statistics": "在統計資料中忽略此用戶端", + "schedule_services": "暫停封鎖服務", + "schedule_services_desc": "設定封鎖服務過濾器的暫停排程", + "schedule_services_desc_client": "為此用戶端設定封鎖服務過濾器的暫停排程", + "schedule_desc": "設定封鎖服務的無活動期間", + "schedule_invalid_select": "開始時間必須早於結束時間", + "schedule_select_days": "選擇日期", + "schedule_timezone": "選擇時區", + "schedule_current_timezone": "當前時區:{{value}}", + "schedule_time_all_day": "全天", + "schedule_modal_description": "此排程將取代相同星期中的任何現有排程。每個星期的每一天只能有一個無活動時段。", + "schedule_modal_time_off": "無封鎖服務:", + "schedule_new": "新排程", + "schedule_edit": "編輯排程", + "schedule_save": "儲存排程", + "schedule_add": "新增排程", + "schedule_remove": "刪除排程", + "schedule_from": "從", + "schedule_to": "至", + "sunday": "星期日", + "monday": "星期一", + "tuesday": "星期二", + "wednesday": "星期三", + "thursday": "星期四", + "friday": "星期五", + "saturday": "星期六", + "sunday_short": "週日", + "monday_short": "週一", + "tuesday_short": "週二", + "wednesday_short": "週三", + "thursday_short": "週四", + "friday_short": "週五", + "saturday_short": "週六" } diff --git a/client/src/actions/dnsConfig.js b/client/src/actions/dnsConfig.js index f8141fd5..47823f66 100644 --- a/client/src/actions/dnsConfig.js +++ b/client/src/actions/dnsConfig.js @@ -50,6 +50,10 @@ export const setDnsConfig = (config) => async (dispatch) => { data.bootstrap_dns = splitByNewLine(config.bootstrap_dns); hasDnsSettings = true; } + if (Object.prototype.hasOwnProperty.call(data, 'fallback_dns')) { + data.fallback_dns = splitByNewLine(config.fallback_dns); + hasDnsSettings = true; + } if (Object.prototype.hasOwnProperty.call(data, 'local_ptr_upstreams')) { data.local_ptr_upstreams = splitByNewLine(config.local_ptr_upstreams); hasDnsSettings = true; diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 5d96b045..d403a77e 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -343,7 +343,12 @@ export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE'); export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS'); export const testUpstream = ( - { bootstrap_dns, upstream_dns, local_ptr_upstreams }, upstream_dns_file, + { + bootstrap_dns, + upstream_dns, + local_ptr_upstreams, + fallback_dns, + }, upstream_dns_file, ) => async (dispatch) => { dispatch(testUpstreamRequest()); try { @@ -352,6 +357,7 @@ export const testUpstream = ( const config = { bootstrap_dns: splitByNewLine(bootstrap_dns), private_upstream: splitByNewLine(local_ptr_upstreams), + fallback_dns: splitByNewLine(fallback_dns), ...(upstream_dns_file ? null : { upstream_dns: removeComments(upstream_dns), }), @@ -386,12 +392,14 @@ export const testUpstreamWithFormValues = () => async (dispatch, getState) => { bootstrap_dns, upstream_dns, local_ptr_upstreams, + fallback_dns, } = getState().form[FORM_NAME.UPSTREAM].values; return dispatch(testUpstream({ bootstrap_dns, upstream_dns, local_ptr_upstreams, + fallback_dns, }, upstream_dns_file)); }; diff --git a/client/src/actions/services.js b/client/src/actions/services.js index 2f5f20db..28ae8837 100644 --- a/client/src/actions/services.js +++ b/client/src/actions/services.js @@ -32,19 +32,19 @@ export const getAllBlockedServices = () => async (dispatch) => { } }; -export const setBlockedServicesRequest = createAction('SET_BLOCKED_SERVICES_REQUEST'); -export const setBlockedServicesFailure = createAction('SET_BLOCKED_SERVICES_FAILURE'); -export const setBlockedServicesSuccess = createAction('SET_BLOCKED_SERVICES_SUCCESS'); +export const updateBlockedServicesRequest = createAction('UPDATE_BLOCKED_SERVICES_REQUEST'); +export const updateBlockedServicesFailure = createAction('UPDATE_BLOCKED_SERVICES_FAILURE'); +export const updateBlockedServicesSuccess = createAction('UPDATE_BLOCKED_SERVICES_SUCCESS'); -export const setBlockedServices = (values) => async (dispatch) => { - dispatch(setBlockedServicesRequest()); +export const updateBlockedServices = (values) => async (dispatch) => { + dispatch(updateBlockedServicesRequest()); try { - await apiClient.setBlockedServices(values); - dispatch(setBlockedServicesSuccess()); + await apiClient.updateBlockedServices(values); + dispatch(updateBlockedServicesSuccess()); dispatch(getBlockedServices()); dispatch(addSuccessToast('blocked_services_saved')); } catch (error) { dispatch(addErrorToast({ error })); - dispatch(setBlockedServicesFailure()); + dispatch(updateBlockedServicesFailure()); } }; diff --git a/client/src/actions/stats.js b/client/src/actions/stats.js index 0e5b416e..d0408b63 100644 --- a/client/src/actions/stats.js +++ b/client/src/actions/stats.js @@ -56,6 +56,8 @@ export const getStats = () => async (dispatch) => { top_clients: topClientsWithInfo, top_queried_domains: normalizeTopStats(stats.top_queried_domains), avg_processing_time: secondsToMilliseconds(stats.avg_processing_time), + top_upstreams_responses: normalizeTopStats(stats.top_upstreams_responses), + top_upstrems_avg_time: normalizeTopStats(stats.top_upstreams_avg_time), }; dispatch(getStatsSuccess(normalizedStats)); diff --git a/client/src/api/Api.js b/client/src/api/Api.js index a01c9d04..077c794e 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -489,9 +489,9 @@ class Api { } // Blocked services - BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' }; + BLOCKED_SERVICES_GET = { path: 'blocked_services/get', method: 'GET' }; - BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' }; + BLOCKED_SERVICES_UPDATE = { path: 'blocked_services/update', method: 'PUT' }; BLOCKED_SERVICES_ALL = { path: 'blocked_services/all', method: 'GET' }; @@ -501,12 +501,12 @@ class Api { } getBlockedServices() { - const { path, method } = this.BLOCKED_SERVICES_LIST; + const { path, method } = this.BLOCKED_SERVICES_GET; return this.makeRequest(path, method); } - setBlockedServices(config) { - const { path, method } = this.BLOCKED_SERVICES_SET; + updateBlockedServices(config) { + const { path, method } = this.BLOCKED_SERVICES_UPDATE; const parameters = { data: config, }; diff --git a/client/src/components/App/index.css b/client/src/components/App/index.css index e11400b9..78bd0096 100644 --- a/client/src/components/App/index.css +++ b/client/src/components/App/index.css @@ -89,18 +89,18 @@ body { } .container--wrap { - min-height: calc(100vh - 160px); + min-height: calc(100vh - 372px); +} + +@media screen and (min-width: 768px) { + .container--wrap { + min-height: calc(100vh - 168px); + } } @media screen and (min-width: 992px) { .container--wrap { - min-height: calc(100vh - 117px); - } -} - -@media screen and (max-width: 992px) { - .container--wrap { - min-height: calc(100vh); + min-height: calc(100vh - 187px); } } diff --git a/client/src/components/App/index.js b/client/src/components/App/index.js index d59f985f..797bf1bc 100644 --- a/client/src/components/App/index.js +++ b/client/src/components/App/index.js @@ -192,7 +192,7 @@ const App = () => {
-
+
{processing && } {!isCoreRunning &&
diff --git a/client/src/components/Dashboard/UpstreamAvgTime.js b/client/src/components/Dashboard/UpstreamAvgTime.js new file mode 100644 index 00000000..f56b5165 --- /dev/null +++ b/client/src/components/Dashboard/UpstreamAvgTime.js @@ -0,0 +1,79 @@ +import React from 'react'; +import ReactTable from 'react-table'; +import PropTypes from 'prop-types'; +import round from 'lodash/round'; +import { withTranslation, Trans } from 'react-i18next'; + +import Card from '../ui/Card'; +import DomainCell from './DomainCell'; + +const TimeCell = ({ value }) => { + if (!value) { + return '–'; + } + + const valueInMilliseconds = round(value * 1000); + + return ( +
+ + {valueInMilliseconds} ms + +
+ ); +}; + +TimeCell.propTypes = { + value: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), +}; + +const UpstreamAvgTime = ({ + t, + refreshButton, + topUpstreamsAvgTime, + subtitle, +}) => ( + + ({ + domain, + count, + }))} + columns={[ + { + Header: upstream, + accessor: 'domain', + Cell: DomainCell, + }, + { + Header: processing_time, + accessor: 'count', + maxWidth: 190, + Cell: TimeCell, + }, + ]} + showPagination={false} + noDataText={t('no_upstreams_data_found')} + minRows={6} + defaultPageSize={100} + className="-highlight card-table-overflow--limited stats__table" + /> + +); + +UpstreamAvgTime.propTypes = { + topUpstreamsAvgTime: PropTypes.array.isRequired, + refreshButton: PropTypes.node.isRequired, + subtitle: PropTypes.string.isRequired, + t: PropTypes.func.isRequired, +}; + +export default withTranslation()(UpstreamAvgTime); diff --git a/client/src/components/Dashboard/UpstreamResponses.js b/client/src/components/Dashboard/UpstreamResponses.js new file mode 100644 index 00000000..658d7de8 --- /dev/null +++ b/client/src/components/Dashboard/UpstreamResponses.js @@ -0,0 +1,81 @@ +import React from 'react'; +import ReactTable from 'react-table'; +import PropTypes from 'prop-types'; +import { withTranslation, Trans } from 'react-i18next'; + +import Card from '../ui/Card'; +import Cell from '../ui/Cell'; +import DomainCell from './DomainCell'; + +import { getPercent } from '../../helpers/helpers'; +import { STATUS_COLORS } from '../../helpers/constants'; + +const CountCell = (totalBlocked) => ( + function cell(row) { + const { value } = row; + const percent = getPercent(totalBlocked, value); + + return ( + + ); + } +); + +const getTotalUpstreamRequests = (stats) => { + let total = 0; + stats.forEach(({ count }) => { total += count; }); + + return total; +}; + +const UpstreamResponses = ({ + t, + refreshButton, + topUpstreamsResponses, + subtitle, +}) => ( + + ({ + domain, + count, + }))} + columns={[ + { + Header: upstream, + accessor: 'domain', + Cell: DomainCell, + }, + { + Header: requests_count, + accessor: 'count', + maxWidth: 190, + Cell: CountCell(getTotalUpstreamRequests(topUpstreamsResponses)), + }, + ]} + showPagination={false} + noDataText={t('no_upstreams_data_found')} + minRows={6} + defaultPageSize={100} + className="-highlight card-table-overflow--limited stats__table" + /> + +); + +UpstreamResponses.propTypes = { + topUpstreamsResponses: PropTypes.array.isRequired, + refreshButton: PropTypes.node.isRequired, + subtitle: PropTypes.string.isRequired, + t: PropTypes.func.isRequired, +}; + +export default withTranslation()(UpstreamResponses); diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js index 54354979..c0c88a0e 100644 --- a/client/src/components/Dashboard/index.js +++ b/client/src/components/Dashboard/index.js @@ -21,6 +21,8 @@ import PageTitle from '../ui/PageTitle'; import Loading from '../ui/Loading'; import './Dashboard.css'; import Dropdown from '../ui/Dropdown'; +import UpstreamResponses from './UpstreamResponses'; +import UpstreamAvgTime from './UpstreamAvgTime'; const Dashboard = ({ getAccessList, @@ -136,12 +138,12 @@ const Dashboard = ({
@@ -185,53 +187,67 @@ const Dashboard = ({
)}
+
+
+ +
+
+
} diff --git a/client/src/components/Filters/Form.js b/client/src/components/Filters/Form.js index f4e902f5..5619580b 100644 --- a/client/src/components/Filters/Form.js +++ b/client/src/components/Filters/Form.js @@ -134,7 +134,6 @@ const Form = (props) => { component={renderInputField} className="form-control" placeholder={t('enter_name_hint')} - validate={[validateRequiredValue]} normalizeOnBlur={(data) => data.trim()} />
diff --git a/client/src/components/Filters/Services/ScheduleForm/Modal.js b/client/src/components/Filters/Services/ScheduleForm/Modal.js new file mode 100644 index 00000000..429db9be --- /dev/null +++ b/client/src/components/Filters/Services/ScheduleForm/Modal.js @@ -0,0 +1,220 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { useTranslation } from 'react-i18next'; +import ReactModal from 'react-modal'; + +import { Timezone } from './Timezone'; +import { TimeSelect } from './TimeSelect'; +import { TimePeriod } from './TimePeriod'; +import { getFullDayName, getShortDayName } from './helpers'; +import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants'; + +export const DAYS_OF_WEEK = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; + +const INITIAL_START_TIME_MS = 0; +const INITIAL_END_TIME_MS = 86340000; + +export const Modal = ({ + isOpen, + currentDay, + schedule, + onClose, + onSubmit, +}) => { + const [t] = useTranslation(); + + const intialTimezone = schedule.time_zone === LOCAL_TIMEZONE_VALUE + ? Intl.DateTimeFormat().resolvedOptions().timeZone + : schedule.time_zone; + + const [timezone, setTimezone] = useState(intialTimezone); + const [days, setDays] = useState(new Set()); + + const [startTime, setStartTime] = useState(INITIAL_START_TIME_MS); + const [endTime, setEndTime] = useState(INITIAL_END_TIME_MS); + + const [wrongPeriod, setWrongPeriod] = useState(true); + + useEffect(() => { + if (currentDay) { + const newDays = new Set([currentDay]); + setDays(newDays); + + setStartTime(schedule[currentDay].start); + setEndTime(schedule[currentDay].end); + } + }, [currentDay]); + + useEffect(() => { + if (startTime >= endTime) { + setWrongPeriod(true); + } else { + setWrongPeriod(false); + } + }, [startTime, endTime]); + + const addDays = (day) => { + const newDays = new Set(days); + + if (newDays.has(day)) { + newDays.delete(day); + } else { + newDays.add(day); + } + + setDays(newDays); + }; + + const activeDay = (day) => { + return days.has(day); + }; + + const onFormSubmit = (e) => { + e.preventDefault(); + + if (currentDay) { + const newSchedule = schedule; + + Array.from(days).forEach((day) => { + newSchedule[day] = { + start: startTime, + end: endTime, + }; + }); + + onSubmit(newSchedule); + } else { + const newSchedule = { + time_zone: timezone, + }; + + Array.from(days).forEach((day) => { + newSchedule[day] = { + start: startTime, + end: endTime, + }; + }); + + onSubmit(newSchedule); + } + }; + + return ( + +
+
+

+ {currentDay ? t('schedule_edit') : t('schedule_new')} +

+ +
+
+
+ + +
+ {DAYS_OF_WEEK.map((day) => ( + + )) } +
+ +
+
+ setStartTime(v)} + /> + + setEndTime(v)} + /> +
+ + {wrongPeriod && ( +
+ {t('schedule_invalid_select')} +
+ )} +
+ +
+
+ {t('schedule_modal_time_off')} +
+
+ + + + {days.size ? ( + Array.from(days).map((day) => getFullDayName(t, day)).join(', ') + ) : ( + + — + + )} +
+
+ + + + {wrongPeriod ? ( + + — + + ) : ( + + )} +
+
+ +
+ {t('schedule_modal_description')} +
+
+
+
+ +
+
+
+
+
+ ); +}; + +Modal.propTypes = { + schedule: PropTypes.object.isRequired, + currentDay: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, +}; diff --git a/client/src/components/Filters/Services/ScheduleForm/TimePeriod.js b/client/src/components/Filters/Services/ScheduleForm/TimePeriod.js new file mode 100644 index 00000000..69ef2293 --- /dev/null +++ b/client/src/components/Filters/Services/ScheduleForm/TimePeriod.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { getTimeFromMs } from './helpers'; + +export const TimePeriod = ({ + startTimeMs, + endTimeMs, +}) => { + const startTime = getTimeFromMs(startTimeMs); + const endTime = getTimeFromMs(endTimeMs); + + return ( +
+ +  –  + +
+ ); +}; + +TimePeriod.propTypes = { + startTimeMs: PropTypes.number.isRequired, + endTimeMs: PropTypes.number.isRequired, +}; diff --git a/client/src/components/Filters/Services/ScheduleForm/TimeSelect.js b/client/src/components/Filters/Services/ScheduleForm/TimeSelect.js new file mode 100644 index 00000000..35998437 --- /dev/null +++ b/client/src/components/Filters/Services/ScheduleForm/TimeSelect.js @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; + +import { getTimeFromMs, convertTimeToMs } from './helpers'; + +export const TimeSelect = ({ + value, + onChange, +}) => { + const { hours: initialHours, minutes: initialMinutes } = getTimeFromMs(value); + + const [hours, setHours] = useState(initialHours); + const [minutes, setMinutes] = useState(initialMinutes); + + const hourOptions = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0')); + const minuteOptions = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0')); + + const onHourChange = (event) => { + setHours(event.target.value); + onChange(convertTimeToMs(event.target.value, minutes)); + }; + + const onMinuteChange = (event) => { + setMinutes(event.target.value); + onChange(convertTimeToMs(hours, event.target.value)); + }; + + return ( +
+ +  :  + +
+ ); +}; + +TimeSelect.propTypes = { + value: PropTypes.number.isRequired, + onChange: PropTypes.func.isRequired, +}; diff --git a/client/src/components/Filters/Services/ScheduleForm/Timezone.js b/client/src/components/Filters/Services/ScheduleForm/Timezone.js new file mode 100644 index 00000000..71d017d6 --- /dev/null +++ b/client/src/components/Filters/Services/ScheduleForm/Timezone.js @@ -0,0 +1,46 @@ +import React from 'react'; +import timezones from 'timezones-list'; +import { useTranslation } from 'react-i18next'; +import PropTypes from 'prop-types'; + +import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants'; + +export const Timezone = ({ + timezone, + setTimezone, +}) => { + const [t] = useTranslation(); + + const onTimeZoneChange = (event) => { + setTimezone(event.target.value); + }; + + return ( +
+ + + +
+ ); +}; + +Timezone.propTypes = { + timezone: PropTypes.string.isRequired, + setTimezone: PropTypes.func.isRequired, +}; diff --git a/client/src/components/Filters/Services/ScheduleForm/helpers.js b/client/src/components/Filters/Services/ScheduleForm/helpers.js new file mode 100644 index 00000000..c3986ed4 --- /dev/null +++ b/client/src/components/Filters/Services/ScheduleForm/helpers.js @@ -0,0 +1,46 @@ +export const getFullDayName = (t, abbreviation) => { + const dayMap = { + sun: t('sunday'), + mon: t('monday'), + tue: t('tuesday'), + wed: t('wednesday'), + thu: t('thursday'), + fri: t('friday'), + sat: t('saturday'), + }; + + return dayMap[abbreviation] || ''; +}; + +export const getShortDayName = (t, abbreviation) => { + const dayMap = { + sun: t('sunday_short'), + mon: t('monday_short'), + tue: t('tuesday_short'), + wed: t('wednesday_short'), + thu: t('thursday_short'), + fri: t('friday_short'), + sat: t('saturday_short'), + }; + + return dayMap[abbreviation] || ''; +}; + +export const getTimeFromMs = (value) => { + const selectedTime = new Date(value); + const hours = selectedTime.getUTCHours(); + const minutes = selectedTime.getUTCMinutes(); + + return { + hours: hours.toString().padStart(2, '0'), + minutes: minutes.toString().padStart(2, '0'), + }; +}; + +export const convertTimeToMs = (hours, minutes) => { + const selectedTime = new Date(0); + selectedTime.setUTCHours(parseInt(hours, 10)); + selectedTime.setUTCMinutes(parseInt(minutes, 10)); + + return selectedTime.getTime(); +}; diff --git a/client/src/components/Filters/Services/ScheduleForm/index.js b/client/src/components/Filters/Services/ScheduleForm/index.js new file mode 100644 index 00000000..f7bf605b --- /dev/null +++ b/client/src/components/Filters/Services/ScheduleForm/index.js @@ -0,0 +1,140 @@ +import React, { useState, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import PropTypes from 'prop-types'; +import cn from 'classnames'; + +import { Modal } from './Modal'; +import { getFullDayName, getShortDayName } from './helpers'; +import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants'; +import { TimePeriod } from './TimePeriod'; +import './styles.css'; + +export const ScheduleForm = ({ + schedule, + onScheduleSubmit, + clientForm, +}) => { + const [t] = useTranslation(); + const [modalOpen, setModalOpen] = useState(false); + const [currentDay, setCurrentDay] = useState(); + + const onModalOpen = () => setModalOpen(true); + const onModalClose = () => setModalOpen(false); + + const filteredScheduleKeys = useMemo(() => ( + schedule ? Object.keys(schedule).filter((v) => v !== 'time_zone') : [] + ), [schedule]); + + const scheduleMap = new Map(); + filteredScheduleKeys.forEach((day) => scheduleMap.set(day, schedule[day])); + + const onSubmit = (values) => { + onScheduleSubmit(values); + onModalClose(); + }; + + const onDelete = (day) => { + scheduleMap.delete(day); + + const scheduleWeek = Object.fromEntries(Array.from(scheduleMap.entries())); + + onScheduleSubmit({ + time_zone: schedule.time_zone, + ...scheduleWeek, + }); + }; + + const onEdit = (day) => { + setCurrentDay(day); + onModalOpen(); + }; + + const onAdd = () => { + setCurrentDay(undefined); + onModalOpen(); + }; + + return ( +
+
+ {t('schedule_current_timezone', { value: schedule?.time_zone || LOCAL_TIMEZONE_VALUE })} +
+ +
+ {filteredScheduleKeys.map((day) => { + const data = schedule[day]; + + if (!data) { + return undefined; + } + + return ( +
+
+ {getFullDayName(t, day)} +
+
+ {getShortDayName(t, day)} +
+ +
+ + + +
+
+ ); + })} +
+ + + + {modalOpen && ( + + )} +
+ ); +}; + +ScheduleForm.propTypes = { + schedule: PropTypes.object, + onScheduleSubmit: PropTypes.func.isRequired, + clientForm: PropTypes.bool, +}; diff --git a/client/src/components/Filters/Services/ScheduleForm/styles.css b/client/src/components/Filters/Services/ScheduleForm/styles.css new file mode 100644 index 00000000..f839f92a --- /dev/null +++ b/client/src/components/Filters/Services/ScheduleForm/styles.css @@ -0,0 +1,134 @@ +.schedule__row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.schedule__row:last-child { + margin-bottom: 0; +} + +.schedule__rows { + margin-bottom: 24px; +} + +.schedule__day { + display: none; + min-width: 110px; +} + +.schedule__day--mobile { + display: block; + min-width: 50px; +} + +@media screen and (min-width: 767px) { + .schedule__row { + justify-content: flex-start; + } + + .schedule__day { + display: block; + } + + .schedule__day--mobile { + display: none; + } + + .schedule__actions { + margin-left: 32px; + white-space: nowrap; + } +} + +.schedule__time { + min-width: 110px; +} + +.schedule__button { + border: 0; +} + +.schedule__days { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 24px; +} + +.schedule__button-day { + display: flex; + justify-content: center; + align-items: center; + min-width: 60px; + height: 32px; + font-size: 14px; + line-height: 14px; + color: #495057; + background-color: transparent; + border: 1px solid var(--card-border-color); + border-radius: 4px; + cursor: pointer; + outline: 0; +} + +.schedule__button-day[data-active="true"] { + color: var(--btn-success-bgcolor); + border-color: var(--btn-success-bgcolor); +} + +.schedule__time-wrap { + margin-bottom: 24px; +} + +.schedule__time-row { + display: flex; + align-items: center; + gap: 16px; +} + +.schedule__time-select { + display: flex; + align-items: center; +} + +.schedule__error { + margin-top: 4px; + font-size: 13px; + color: #cd201f; +} + +.schedule__timezone { + margin-bottom: 24px; +} + +.schedule__current-timezone { + margin-bottom: 16px; +} + +.schedule__info { + margin-bottom: 24px; +} + +.schedule__notice { + font-size: 13px; +} + +.schedule__info-title { + margin-bottom: 8px; +} + +.schedule__info-row { + display: flex; + align-items: center; + margin-bottom: 4px; +} + +.schedule__info-icon { + width: 24px; + height: 24px; + margin-right: 8px; + color: #495057; + flex-shrink: 0; +} diff --git a/client/src/components/Filters/Services/index.js b/client/src/components/Filters/Services/index.js index 09cdd7c8..3a0a12fd 100644 --- a/client/src/components/Filters/Services/index.js +++ b/client/src/components/Filters/Services/index.js @@ -4,9 +4,11 @@ import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import Form from './Form'; import Card from '../../ui/Card'; -import { getBlockedServices, getAllBlockedServices, setBlockedServices } from '../../../actions/services'; +import { getBlockedServices, getAllBlockedServices, updateBlockedServices } from '../../../actions/services'; import PageTitle from '../../ui/PageTitle'; +import { ScheduleForm } from './ScheduleForm'; + const getInitialDataForServices = (initial) => (initial ? initial.reduce( (acc, service) => { acc.blocked_services[service] = true; @@ -33,10 +35,24 @@ const Services = () => { .keys(values.blocked_services) .filter((service) => values.blocked_services[service]); - dispatch(setBlockedServices(blocked_services)); + dispatch(updateBlockedServices({ + ids: blocked_services, + schedule: services.list.schedule, + })); }; - const initialValues = getInitialDataForServices(services.list); + const handleScheduleSubmit = (values) => { + dispatch(updateBlockedServices({ + ids: services.list.ids, + schedule: values, + })); + }; + + const initialValues = getInitialDataForServices(services.list.ids); + + if (!initialValues) { + return null; + } return ( <> @@ -57,6 +73,17 @@ const Services = () => { /> + + + + ); }; diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css index 754f9fdb..762dfa50 100644 --- a/client/src/components/Logs/Logs.css +++ b/client/src/components/Logs/Logs.css @@ -74,6 +74,12 @@ color: #295a9f; } +[data-theme="dark"] .logs__text--link, +[data-theme="dark"] .logs__text--link:hover, +[data-theme="dark"] .logs__text--link:focus { + color: var(--gray-f3); +} + .icon--selected { background-color: var(--gray-f3); border: solid 1px var(--gray-d8); diff --git a/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js index dab8994c..96f835c3 100644 --- a/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js +++ b/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js @@ -6,7 +6,7 @@ import { Trans, useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import ReactTable from 'react-table'; -import { getAllBlockedServices } from '../../../../actions/services'; +import { getAllBlockedServices, getBlockedServices } from '../../../../actions/services'; import { initSettings } from '../../../../actions'; import { splitByNewLine, @@ -14,7 +14,7 @@ import { sortIp, getService, } from '../../../../helpers/helpers'; -import { MODAL_TYPE } from '../../../../helpers/constants'; +import { MODAL_TYPE, LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants'; import Card from '../../../ui/Card'; import CellWrap from '../../../ui/CellWrap'; import LogsSearchLink from '../../../ui/LogsSearchLink'; @@ -45,6 +45,7 @@ const ClientsTable = ({ useEffect(() => { dispatch(getAllBlockedServices()); + dispatch(getBlockedServices()); dispatch(initSettings()); }, []); @@ -112,6 +113,9 @@ const ClientsTable = ({ tags: [], use_global_settings: true, use_global_blocked_services: true, + blocked_services_schedule: { + time_zone: LOCAL_TIMEZONE_VALUE, + }, safe_search: { ...(safesearch || {}) }, }; }; diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js index 190996e4..652957d0 100644 --- a/client/src/components/Settings/Clients/Form.js +++ b/client/src/components/Settings/Clients/Form.js @@ -11,6 +11,7 @@ import Select from 'react-select'; import i18n from '../../../i18n'; import Tabs from '../../ui/Tabs'; import Examples from '../Dns/Upstream/Examples'; +import { ScheduleForm } from '../../Filters/Services/ScheduleForm'; import { toggleAllServices, trimLinesAndRemoveEmpty, captitalizeWords } from '../../../helpers/helpers'; import { renderInputField, @@ -137,10 +138,10 @@ let Form = (props) => { handleSubmit, reset, change, - pristine, submitting, useGlobalSettings, useGlobalServices, + blockedServicesSchedule, toggleClientModal, processingAdding, processingUpdating, @@ -155,6 +156,10 @@ let Form = (props) => { const [activeTabLabel, setActiveTabLabel] = useState('settings'); + const handleScheduleSubmit = (values) => { + change('blocked_services_schedule', values); + }; + const tabs = { settings: { title: 'settings', @@ -269,6 +274,21 @@ let Form = (props) => { , }, + schedule_services: { + title: 'schedule_services', + component: ( + <> +
+ schedule_services_desc_client +
+ + + ), + }, upstream_dns: { title: 'upstream_dns', component:
@@ -355,8 +375,12 @@ let Form = (props) => {
- + {activeTab} @@ -380,7 +404,6 @@ let Form = (props) => { disabled={ submitting || invalid - || pristine || processingAdding || processingUpdating } @@ -402,6 +425,7 @@ Form.propTypes = { toggleClientModal: PropTypes.func.isRequired, useGlobalSettings: PropTypes.bool, useGlobalServices: PropTypes.bool, + blockedServicesSchedule: PropTypes.object, t: PropTypes.func.isRequired, processingAdding: PropTypes.bool.isRequired, processingUpdating: PropTypes.bool.isRequired, @@ -415,9 +439,11 @@ const selector = formValueSelector(FORM_NAME.CLIENT); Form = connect((state) => { const useGlobalSettings = selector(state, 'use_global_settings'); const useGlobalServices = selector(state, 'use_global_blocked_services'); + const blockedServicesSchedule = selector(state, 'blocked_services_schedule'); return { useGlobalSettings, useGlobalServices, + blockedServicesSchedule, }; })(Form); diff --git a/client/src/components/Settings/Dns/Upstream/Form.js b/client/src/components/Settings/Dns/Upstream/Form.js index be01b2b7..3c534f0c 100644 --- a/client/src/components/Settings/Dns/Upstream/Form.js +++ b/client/src/components/Settings/Dns/Upstream/Form.js @@ -179,6 +179,30 @@ const Form = ({
+
+ +
+ fallback_dns_desc +
+ +
+
+
+
diff --git a/client/src/components/Settings/StatsConfig/Form.js b/client/src/components/Settings/StatsConfig/Form.js index 6512639e..857c6d79 100644 --- a/client/src/components/Settings/StatsConfig/Form.js +++ b/client/src/components/Settings/StatsConfig/Form.js @@ -24,6 +24,7 @@ import { CUSTOM_INTERVAL, RETENTION_RANGE, } from '../../../helpers/constants'; +import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers'; import '../FormButton.css'; const getIntervalTitle = (intervalMs, t) => { @@ -135,6 +136,7 @@ let Form = (props) => { component={renderTextareaField} placeholder={t('ignore_domains')} disabled={processing} + normalizeOnBlur={trimLinesAndRemoveEmpty} />
diff --git a/client/src/components/ui/Cell.js b/client/src/components/ui/Cell.js index 9ede87d5..4fb850d6 100644 --- a/client/src/components/ui/Cell.js +++ b/client/src/components/ui/Cell.js @@ -1,25 +1,39 @@ import React from 'react'; import PropTypes from 'prop-types'; + import LogsSearchLink from './LogsSearchLink'; import { formatNumber } from '../../helpers/helpers'; const Cell = ({ - value, percent, color, search, -}) =>
-
- {formatNumber(value)} - {percent}% + value, + percent, + color, + search, +}) => ( +
+
+ + {search ? ( + + {formatNumber(value)} + + ) : ( + formatNumber(value) + )} + + {percent}% +
+
+
+
-
-
-
-
; +); Cell.propTypes = { value: PropTypes.number.isRequired, diff --git a/client/src/components/ui/Icons.js b/client/src/components/ui/Icons.js index b38da489..288eadfc 100644 --- a/client/src/components/ui/Icons.js +++ b/client/src/components/ui/Icons.js @@ -225,6 +225,20 @@ const Icons = () => ( + + + + + + + + + + + + + + ); diff --git a/client/src/components/ui/Line.js b/client/src/components/ui/Line.js index ffc218d7..6e13b2e3 100644 --- a/client/src/components/ui/Line.js +++ b/client/src/components/ui/Line.js @@ -44,7 +44,7 @@ const Line = ({ enableGridY={false} enablePoints={false} xFormat={(x) => { - if (interval === 1 || interval === 7) { + if (interval >= 0 && interval <= 7) { const hoursAgo = subHours(Date.now(), 24 * interval); return dateFormat(addHours(hoursAgo, x), 'D MMM HH:00'); } diff --git a/client/src/components/ui/Modal.css b/client/src/components/ui/Modal.css index b22b9709..13adb794 100644 --- a/client/src/components/ui/Modal.css +++ b/client/src/components/ui/Modal.css @@ -41,7 +41,8 @@ } @media (min-width: 576px) { - .modal-dialog--clients { + .modal-dialog--clients, + .modal-dialog--schedule { max-width: 650px; } } diff --git a/client/src/components/ui/PageTitle.css b/client/src/components/ui/PageTitle.css index a7e2c284..57966d75 100644 --- a/client/src/components/ui/PageTitle.css +++ b/client/src/components/ui/PageTitle.css @@ -6,7 +6,7 @@ .page-header--logs { flex-direction: row; align-items: flex-end; - margin: 2rem 0 2.8rem; + margin: 0.5rem 0 2.8rem; } .page-header--logs .page-title { @@ -18,7 +18,7 @@ .page-header--logs { flex-direction: column; align-items: center; - margin: 1.1rem 0; + margin-bottom: 0 0 1.1rem; } .page-header--logs .page-title { diff --git a/client/src/components/ui/Tabler.css b/client/src/components/ui/Tabler.css index 9ad1ff35..d49810e2 100644 --- a/client/src/components/ui/Tabler.css +++ b/client/src/components/ui/Tabler.css @@ -470,6 +470,10 @@ hr { border-top: 1px solid rgba(0, 40, 100, 0.12); } +[data-theme=dark] hr { + border-color: var(--card-border-color); +} + small, .small { font-size: 87.5%; @@ -10204,7 +10208,7 @@ body.fixed-header .page { -ms-flex-align: center; align-items: center; flex-direction: column; - margin: 1.5rem 0 1.5rem; + margin: 0 0 1.5rem; -ms-flex-wrap: wrap; flex-wrap: wrap; } diff --git a/client/src/containers/Settings.js b/client/src/containers/Settings.js index 866765de..6222f8eb 100644 --- a/client/src/containers/Settings.js +++ b/client/src/containers/Settings.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import { initSettings, toggleSetting } from '../actions'; -import { getBlockedServices, setBlockedServices } from '../actions/services'; +import { getBlockedServices, updateBlockedServices } from '../actions/services'; import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats'; import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs'; import { getFilteringStatus, setFiltersConfig } from '../actions/filtering'; @@ -24,7 +24,7 @@ const mapDispatchToProps = { initSettings, toggleSetting, getBlockedServices, - setBlockedServices, + updateBlockedServices, getStatsConfig, setStatsConfig, resetStats, diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index ec1e437e..e436d77f 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -552,3 +552,5 @@ export const DISABLE_PROTECTION_TIMINGS = { }; export const LOCAL_STORAGE_THEME_KEY = 'account_theme'; + +export const LOCAL_TIMEZONE_VALUE = 'Local'; diff --git a/client/src/helpers/filters/filters.js b/client/src/helpers/filters/filters.js index 8bc5a230..bd140867 100644 --- a/client/src/helpers/filters/filters.js +++ b/client/src/helpers/filters/filters.js @@ -64,6 +64,12 @@ export default { "homepage": "https://github.com/MasterKia/PersianBlocker", "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_19.txt" }, + "ISR_easyList_hebrew": { + "name": "ISR: EasyList Hebrew", + "categoryId": "regional", + "homepage": "https://github.com/easylist/EasyListHebrew", + "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_43.txt" + }, "KOR_list_kr": { "name": "KOR: List-KR DNS", "categoryId": "regional", @@ -172,12 +178,36 @@ export default { "homepage": "https://github.com/DandelionSprout/adfilt", "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_6.txt" }, + "hagezi_allowlist_referral": { + "name": "HaGeZi's Allowlist Referral", + "categoryId": "other", + "homepage": "https://github.com/hagezi/dns-blocklists#referral", + "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_45.txt" + }, + "hagezi_antipiracy_blocklist": { + "name": "HaGeZi's Anti-Piracy Blocklist", + "categoryId": "other", + "homepage": "https://github.com/hagezi/dns-blocklists#piracy", + "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_46.txt" + }, + "hagezi_gambling_blocklist": { + "name": "HaGeZi's Gambling Blocklist", + "categoryId": "other", + "homepage": "https://github.com/hagezi/dns-blocklists#gambling", + "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_47.txt" + }, "hagezi_multinormal": { "name": "HaGeZi Multi NORMAL", "categoryId": "general", "homepage": "https://github.com/hagezi/dns-blocklists", "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt" }, + "hagezi_threat_intelligence_feeds": { + "name": "HaGeZi's Threat Intelligence Feeds", + "categoryId": "security", + "homepage": "https://github.com/hagezi/dns-blocklists", + "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_44.txt" + }, "no_google": { "name": "No Google", "categoryId": "other", @@ -220,6 +250,12 @@ export default { "homepage": "https://pgl.yoyo.org/adservers/", "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_3.txt" }, + "phishing_army": { + "name": "Phishing Army", + "categoryId": "security", + "homepage": "https://gitlab.com/malware-filter/phishing-filter", + "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt" + }, "scam_blocklist_by_durablenapkin": { "name": "Scam Blocklist by DurableNapkin", "categoryId": "security", diff --git a/client/src/helpers/trackers/trackers.json b/client/src/helpers/trackers/trackers.json index e061c1d4..966e7437 100644 --- a/client/src/helpers/trackers/trackers.json +++ b/client/src/helpers/trackers/trackers.json @@ -1,5 +1,5 @@ { - "timeUpdated": "2023-08-01T00:10:42.759Z", + "timeUpdated": "2023-09-01T00:09:16.088Z", "categories": { "0": "audio_video_player", "1": "comments", @@ -181,7 +181,7 @@ "name": "7plus", "categoryId": 0, "url": "https://7plus.com.au/", - "companyId": "7plus", + "companyId": "seven_group_holdings", "source": "AdGuard" }, "7tv.de": { @@ -994,10 +994,10 @@ "companyId": "adjug" }, "adjust": { - "name": "Adjust", + "name": "Adjust GmbH", "categoryId": 101, "url": "https://www.adjust.com/", - "companyId": "adjust", + "companyId": "applovin", "source": "AdGuard" }, "adk2": { @@ -2234,8 +2234,9 @@ "alenty": { "name": "Alenty", "categoryId": 4, - "url": "http://www.alenty.com/", - "companyId": "appnexus" + "url": "https://about.ads.microsoft.com/en-us/solutions/xandr/xandr-premium-programmatic-advertising", + "companyId": "microsoft", + "source": "AdGuard" }, "alephd.com": { "name": "alephd", @@ -2617,8 +2618,9 @@ "appnexus": { "name": "AppNexus", "categoryId": 4, - "url": "http://www.appnexus.com/", - "companyId": "appnexus" + "url": "https://about.ads.microsoft.com/en-us/solutions/xandr/xandr-premium-programmatic-advertising", + "companyId": "microsoft", + "source": "AdGuard" }, "appsflyer": { "name": "AppsFlyer", @@ -3359,7 +3361,7 @@ "name": "Binge", "categoryId": 0, "url": "https://binge.com.au/", - "companyId": "foxtel", + "companyId": "news_corp", "source": "AdGuard" }, "binlayer": { @@ -7196,7 +7198,7 @@ "name": "Flash", "categoryId": 0, "url": "https://flashnews.com.au/", - "companyId": "foxtel", + "companyId": "news_corp", "source": "AdGuard" }, "flashtalking": { @@ -7408,7 +7410,7 @@ "name": "Fox Sports", "categoryId": 0, "url": "https://foxsports.com.au/", - "companyId": "foxtel", + "companyId": "news_corp", "source": "AdGuard" }, "foxnews_static": { @@ -7427,7 +7429,7 @@ "name": "Foxtel", "categoryId": 0, "url": "https://foxtel.com.au/", - "companyId": "foxtel", + "companyId": "news_corp", "source": "AdGuard" }, "foxydeal_com": { @@ -7834,10 +7836,11 @@ "companyId": null }, "github": { - "name": "GitHub", + "name": "GitHub, Inc.", "categoryId": 2, "url": "https://github.com/", - "companyId": "github" + "companyId": "microsoft", + "source": "AdGuard" }, "github_apps": { "name": "GitHub Apps", @@ -9529,10 +9532,11 @@ "source": "AdGuard" }, "ironsource": { - "name": "ironSource", + "name": "ironSource Ltd.", "categoryId": 4, - "url": "https://www.ironsrc.com/", - "companyId": "ironsource" + "url": "https://www.is.com", + "companyId": "unity", + "source": "AdGuard" }, "isocket": { "name": "isocket", @@ -9924,7 +9928,7 @@ "name": "Kayo Sports", "categoryId": 0, "url": "https://kayosports.com.au/", - "companyId": "foxtel", + "companyId": "news_corp", "source": "AdGuard" }, "keen_io": { @@ -11251,10 +11255,11 @@ "companyId": "mediaad" }, "mediaglu": { - "name": "Mediaglu", + "name": "MediaGlu", "categoryId": 4, - "url": "http://mlnadvertising.com/", - "companyId": "appnexus" + "url": "https://www.mediaglu.com/", + "companyId": "microsoft", + "source": "AdGuard" }, "mediahub": { "name": "MediaHub", @@ -12762,8 +12767,9 @@ "open_adstream": { "name": "Open Adstream", "categoryId": 4, - "url": "https://www.appnexus.com/en", - "companyId": "appnexus" + "url": "https://about.ads.microsoft.com/en-us/solutions/xandr/xandr-premium-programmatic-advertising", + "companyId": "microsoft", + "source": "AdGuard" }, "open_share_count": { "name": "Open Share Count", @@ -14345,6 +14351,13 @@ "url": "http://www.rdstation.com/en/", "companyId": "rd_station" }, + "rea_group": { + "name": "REA Group Ltd.", + "categoryId": 4, + "url": "https://www.rea-group.com/", + "companyId": "news_corp", + "source": "AdGuard" + }, "reachforce": { "name": "ReachForce", "categoryId": 6, @@ -14405,6 +14418,13 @@ "url": "http://www.realclick.co.kr/", "companyId": "realclick" }, + "realestate.com.au": { + "name": "realestate.com.au Pty Limited", + "categoryId": 4, + "url": "https://www.realestate.com.au/", + "companyId": "news_corp", + "source": "AdGuard" + }, "realperson.de": { "name": "Realperson Chat", "categoryId": 2, @@ -15298,7 +15318,7 @@ "sectigo": { "name": "Sectigo Limited", "categoryId": 5, - "url": "https://www.solaredge.com/", + "url": "https://www.sectigo.com", "companyId": "sectigo", "source": "AdGuard" }, @@ -16657,7 +16677,7 @@ "name": "Streamotion", "categoryId": 0, "url": "https://streamotion.com.au/", - "companyId": "foxtel", + "companyId": "news_corp", "source": "AdGuard" }, "streamrail.com": { @@ -17762,10 +17782,11 @@ "companyId": "amazon_associates" }, "twitter": { - "name": "Twitter", + "name": "X (formerly Twitter)", "categoryId": 7, "url": "https://twitter.com", - "companyId": "twitter" + "companyId": "twitter", + "source": "AdGuard" }, "twitter_ads": { "name": "Twitter Advertising", @@ -18746,6 +18767,13 @@ "url": null, "companyId": null }, + "vungle": { + "name": "Vungle", + "categoryId": 4, + "url": "https://vungle.com/", + "companyId": "blackstone", + "source": "AdGuard" + }, "vuukle": { "name": "Vuukle", "categoryId": 6, @@ -22457,7 +22485,10 @@ "delivery47.com": "ironsource", "ironsrc.com": "ironsource", "ironsrc.net": "ironsource", + "is.com": "ironsource", + "soom.la": "ironsource", "supersonicads.com": "ironsource", + "tapjoy.com": "ironsource", "adsbyisocket.com": "isocket", "isocket.com": "isocket", "isolarcloud.com": "isolarcloud", @@ -23571,6 +23602,7 @@ "rcs.it": "rcs.it", "rcsmediagroup.it": "rcs.it", "d335luupugsy2.cloudfront.net": "rd_station", + "rea-group.com": "rea_group", "d12ulf131zb0yj.cloudfront.net": "reachforce", "reachforce.com": "reachforce", "reachjunction.com": "reachjunction", @@ -23586,6 +23618,7 @@ "readme.io": "readme", "readspeaker.com": "readspeaker.com", "realclick.co.kr": "realclick", + "realestate.com.au": "realestate.com.au", "realperson.de": "realperson.de", "powermarketing.com": "realtime", "realtime.co": "realtime", @@ -24349,6 +24382,9 @@ "trafmag.com": "trafmag.com", "api.transcend.io": "transcend", "cdn.transcend.io": "transcend", + "sync-transcend-cdn.com": "transcend", + "transcend-cdn.com": "transcend", + "transcend.io": "transcend", "telemetry.transcend.io": "transcend_telemetry", "backoffice.transmatico.com": "transmatic", "travelaudience.com": "travel_audience", @@ -24636,6 +24672,8 @@ "v0cdn.net": "vscode", "vscode-cdn.net": "vscode", "vtracy.de": "vtracy.de", + "liftoff.io": "vungle", + "vungle.com": "vungle", "vuukle.com": "vuukle", "view.vzaar.com": "vzaar", "w3counter.com": "w3counter", diff --git a/client/src/install/Setup/Setup.css b/client/src/install/Setup/Setup.css index 0943febc..dc6e19d6 100644 --- a/client/src/install/Setup/Setup.css +++ b/client/src/install/Setup/Setup.css @@ -6,13 +6,14 @@ } .setup { - min-height: calc(100vh - 71px); + min-height: calc(100vh - 345px); line-height: 1.48; } @media screen and (min-width: 768px) { .setup { padding: 50px 0; + min-height: calc(100vh - 141px); } } diff --git a/client/src/reducers/dnsConfig.js b/client/src/reducers/dnsConfig.js index fbc3afdb..47dd4d67 100644 --- a/client/src/reducers/dnsConfig.js +++ b/client/src/reducers/dnsConfig.js @@ -15,6 +15,7 @@ const dnsConfig = handleActions( blocking_ipv4, blocking_ipv6, upstream_dns, + fallback_dns, bootstrap_dns, local_ptr_upstreams, ...values @@ -26,6 +27,7 @@ const dnsConfig = handleActions( blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4, blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6, upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '', + fallback_dns: (fallback_dns && fallback_dns.join('\n')) || '', bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '', local_ptr_upstreams: (local_ptr_upstreams && local_ptr_upstreams.join('\n')) || '', processingGetConfig: false, diff --git a/client/src/reducers/services.js b/client/src/reducers/services.js index c0663c8e..07b9947c 100644 --- a/client/src/reducers/services.js +++ b/client/src/reducers/services.js @@ -20,9 +20,9 @@ const services = handleActions( processingAll: false, }), - [actions.setBlockedServicesRequest]: (state) => ({ ...state, processingSet: true }), - [actions.setBlockedServicesFailure]: (state) => ({ ...state, processingSet: false }), - [actions.setBlockedServicesSuccess]: (state) => ({ + [actions.updateBlockedServicesRequest]: (state) => ({ ...state, processingSet: true }), + [actions.updateBlockedServicesFailure]: (state) => ({ ...state, processingSet: false }), + [actions.updateBlockedServicesSuccess]: (state) => ({ ...state, processingSet: false, }), @@ -31,7 +31,7 @@ const services = handleActions( processing: true, processingAll: true, processingSet: false, - list: [], + list: {}, allServices: [], }, ); diff --git a/client/src/reducers/stats.js b/client/src/reducers/stats.js index a1c63e14..a7baeabe 100644 --- a/client/src/reducers/stats.js +++ b/client/src/reducers/stats.js @@ -58,6 +58,8 @@ const stats = handleActions( num_replaced_safebrowsing: numReplacedSafebrowsing, num_replaced_safesearch: numReplacedSafesearch, avg_processing_time: avgProcessingTime, + top_upstreams_responses: topUpstreamsResponses, + top_upstrems_avg_time: topUpstreamsAvgTime, } = payload; const newState = { @@ -77,6 +79,8 @@ const stats = handleActions( numReplacedSafebrowsing, numReplacedSafesearch, avgProcessingTime, + topUpstreamsResponses, + topUpstreamsAvgTime, }; return newState; diff --git a/go.mod b/go.mod index cf985173..f82c2ca7 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/AdguardTeam/AdGuardHome go 1.20 require ( - github.com/AdguardTeam/dnsproxy v0.52.1-0.20230726165924-30c459b0cdef - github.com/AdguardTeam/golibs v0.13.6 - github.com/AdguardTeam/urlfilter v0.16.1 + github.com/AdguardTeam/dnsproxy v0.54.0 + github.com/AdguardTeam/golibs v0.15.0 + github.com/AdguardTeam/urlfilter v0.17.0 github.com/NYTimes/gziphandler v1.1.1 github.com/ameshkov/dnscrypt/v2 v2.2.7 github.com/bluele/gcache v0.0.2 @@ -16,8 +16,8 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/gopacket v1.1.19 github.com/google/renameio/v2 v2.0.0 - github.com/google/uuid v1.3.0 - github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd + github.com/google/uuid v1.3.1 + github.com/insomniacslk/dhcp v0.0.0-20230816195147-b3ca2534940d github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 github.com/kardianos/service v1.2.2 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 @@ -27,17 +27,14 @@ require ( // own code for that. Perhaps, use gopacket. github.com/mdlayher/raw v0.1.0 github.com/miekg/dns v1.1.55 - // TODO(a.garipov): Update to ≥ v0.37.0 once we update to Go 1.20. - github.com/quic-go/quic-go v0.36.2 + github.com/quic-go/quic-go v0.38.0 github.com/stretchr/testify v1.8.4 github.com/ti-mo/netfilter v0.5.0 go.etcd.io/bbolt v1.3.7 - golang.org/x/crypto v0.11.0 - // TODO(a.garipov): Update after updating slices.Sort and friends to - // stdlib versions in dnsproxy and golibs in Go 1.20. - golang.org/x/exp v0.0.0-20230724220655-d98519c11495 - golang.org/x/net v0.12.0 - golang.org/x/sys v0.10.0 + golang.org/x/crypto v0.12.0 + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 + golang.org/x/net v0.14.0 + golang.org/x/sys v0.11.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 howett.net/plist v1.0.0 @@ -51,19 +48,18 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/mock v1.6.0 // indirect - github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect + github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect github.com/mdlayher/socket v0.4.1 // indirect - github.com/onsi/ginkgo/v2 v2.11.0 // indirect + github.com/onsi/ginkgo/v2 v2.12.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.3.2 // indirect - github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + github.com/quic-go/qtls-go1-20 v0.3.3 // indirect github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/text v0.11.0 // indirect - golang.org/x/tools v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect ) diff --git a/go.sum b/go.sum index cee4fd30..5f527069 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,11 @@ -github.com/AdguardTeam/dnsproxy v0.52.1-0.20230726165924-30c459b0cdef h1:3ZJieG+PV+wJEXLgUndW4yL9/7iubyipbDmA0w3sa7Y= -github.com/AdguardTeam/dnsproxy v0.52.1-0.20230726165924-30c459b0cdef/go.mod h1:Jo2zeRe97Rxt3yikXc+fn0LdLtqCj0Xlyh1PNBj6bpM= -github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= -github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= -github.com/AdguardTeam/golibs v0.13.6 h1:z/0Q25pRLdaQxtoxvfSaooz5mdv8wj0R8KREj54q8yQ= -github.com/AdguardTeam/golibs v0.13.6/go.mod h1:hOtcb8dPfKcFjWTPA904hTA4dl1aWvzeebdJpE72IPk= -github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= -github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw= -github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= +github.com/AdguardTeam/dnsproxy v0.54.0 h1:OgSitM/EKrMMOi+guWZNwaU1cqRqJKWgR3l3fPWWayI= +github.com/AdguardTeam/dnsproxy v0.54.0/go.mod h1:tG/treaQekcKnugYoKOfm8vt3JGi6CliWta0MkQr15U= +github.com/AdguardTeam/golibs v0.15.0 h1:yOv/fdVkJIOWKr0NlUXAE9RA0DK9GKiBbiGzq47vY7o= +github.com/AdguardTeam/golibs v0.15.0/go.mod h1:66ZLs8P7nk/3IfKroQ1rqtieLk+5eXYXMBKXlVL7KeI= +github.com/AdguardTeam/urlfilter v0.17.0 h1:tUzhtR9wMx704GIP3cibsDQJrixlMHfwoQbYJfPdFow= +github.com/AdguardTeam/urlfilter v0.17.0/go.mod h1:bbuZjPUzm/Ip+nz5qPPbwIP+9rZyQbQad8Lt/0fCulU= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= @@ -23,7 +18,6 @@ github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG+ github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -34,8 +28,7 @@ github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu9 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -50,16 +43,16 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= -github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd h1:D772X7igTag7yKErVWAR7boXpOml3fqqBzH1wNaD/jk= -github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= +github.com/insomniacslk/dhcp v0.0.0-20230816195147-b3ca2534940d h1:Ka64cclWedOkGzm9M2/XYuwJUdmWRUozmsxW0PyKA3A= +github.com/insomniacslk/dhcp v0.0.0-20230816195147-b3ca2534940d/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= @@ -67,10 +60,8 @@ github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0 github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE= github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og= github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= @@ -84,14 +75,12 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5 github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -102,42 +91,38 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= -github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= -github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.36.2 h1:ZX/UNQ4gvpCv2RmwdbA6lrRjF6EBm5yZ7TMoT4NQVrA= -github.com/quic-go/quic-go v0.36.2/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ= -github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA= -github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ= +github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= +github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.38.0 h1:T45lASr5q/TrVwt+jrVccmqHhPL2XuSyoCLVCpfOSLc= +github.com/quic-go/quic-go v0.38.0/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg= +github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU= github.com/ti-mo/netfilter v0.5.0 h1:MZmsUw5bFRecOb0AeyjOPxTHg4UxYzyEs0Ek/6Lxoy8= github.com/ti-mo/netfilter v0.5.0/go.mod h1:nt+8B9hx/QpqHr7Hazq+2qMCCA8u2OTkyc/7+U9ARz8= -github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= -github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= -github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg= github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/exp v0.0.0-20230724220655-d98519c11495 h1:zKGKw2WlGb8oPoRGqQ2PT8g2YoCN1w/YbbQjHXCdUWE= -golang.org/x/exp v0.0.0-20230724220655-d98519c11495/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -148,12 +133,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= @@ -162,37 +145,29 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -200,12 +175,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/aghhttp/aghhttp.go b/internal/aghhttp/aghhttp.go index 6cb2c670..88dfe7a7 100644 --- a/internal/aghhttp/aghhttp.go +++ b/internal/aghhttp/aghhttp.go @@ -2,7 +2,6 @@ package aghhttp import ( - "encoding/json" "fmt" "io" "net/http" @@ -61,23 +60,3 @@ func WriteTextPlainDeprecated(w http.ResponseWriter, r *http.Request) (isPlainTe return true } - -// WriteJSONResponse sets the content-type header in w.Header() to -// "application/json", writes a header with a "200 OK" status, encodes resp to -// w, calls [Error] on any returned error, and returns it as well. -func WriteJSONResponse(w http.ResponseWriter, r *http.Request, resp any) (err error) { - return WriteJSONResponseCode(w, r, http.StatusOK, resp) -} - -// WriteJSONResponseCode is like [WriteJSONResponse] but adds the ability to -// redefine the status code. -func WriteJSONResponseCode(w http.ResponseWriter, r *http.Request, code int, resp any) (err error) { - w.Header().Set(httphdr.ContentType, HdrValApplicationJSON) - w.WriteHeader(code) - err = json.NewEncoder(w).Encode(resp) - if err != nil { - Error(r, w, http.StatusInternalServerError, "encoding resp: %s", err) - } - - return err -} diff --git a/internal/aghhttp/header.go b/internal/aghhttp/header.go index eff50473..a1ece086 100644 --- a/internal/aghhttp/header.go +++ b/internal/aghhttp/header.go @@ -4,6 +4,7 @@ package aghhttp // HTTP header value constants. const ( - HdrValApplicationJSON = "application/json" - HdrValTextPlain = "text/plain" + HdrValApplicationJSON = "application/json" + HdrValStrictTransportSecurity = "max-age=31536000; includeSubDomains" + HdrValTextPlain = "text/plain" ) diff --git a/internal/aghhttp/json.go b/internal/aghhttp/json.go new file mode 100644 index 00000000..b7eca767 --- /dev/null +++ b/internal/aghhttp/json.go @@ -0,0 +1,142 @@ +package aghhttp + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/AdguardTeam/golibs/httphdr" + "github.com/AdguardTeam/golibs/log" +) + +// JSON Utilities + +// nsecPerMsec is the number of nanoseconds in a millisecond. +const nsecPerMsec = float64(time.Millisecond / time.Nanosecond) + +// JSONDuration is a time.Duration that can be decoded from JSON and encoded +// into JSON according to our API conventions. +type JSONDuration time.Duration + +// type check +var _ json.Marshaler = JSONDuration(0) + +// MarshalJSON implements the json.Marshaler interface for JSONDuration. err is +// always nil. +func (d JSONDuration) MarshalJSON() (b []byte, err error) { + msec := float64(time.Duration(d)) / nsecPerMsec + b = strconv.AppendFloat(nil, msec, 'f', -1, 64) + + return b, nil +} + +// type check +var _ json.Unmarshaler = (*JSONDuration)(nil) + +// UnmarshalJSON implements the json.Marshaler interface for *JSONDuration. +func (d *JSONDuration) UnmarshalJSON(b []byte) (err error) { + if d == nil { + return fmt.Errorf("json duration is nil") + } + + msec, err := strconv.ParseFloat(string(b), 64) + if err != nil { + return fmt.Errorf("parsing json time: %w", err) + } + + *d = JSONDuration(int64(msec * nsecPerMsec)) + + return nil +} + +// JSONTime is a time.Time that can be decoded from JSON and encoded into JSON +// according to our API conventions. +type JSONTime time.Time + +// type check +var _ json.Marshaler = JSONTime{} + +// MarshalJSON implements the json.Marshaler interface for JSONTime. err is +// always nil. +func (t JSONTime) MarshalJSON() (b []byte, err error) { + msec := float64(time.Time(t).UnixNano()) / nsecPerMsec + b = strconv.AppendFloat(nil, msec, 'f', -1, 64) + + return b, nil +} + +// type check +var _ json.Unmarshaler = (*JSONTime)(nil) + +// UnmarshalJSON implements the json.Marshaler interface for *JSONTime. +func (t *JSONTime) UnmarshalJSON(b []byte) (err error) { + if t == nil { + return fmt.Errorf("json time is nil") + } + + msec, err := strconv.ParseFloat(string(b), 64) + if err != nil { + return fmt.Errorf("parsing json time: %w", err) + } + + *t = JSONTime(time.Unix(0, int64(msec*nsecPerMsec)).UTC()) + + return nil +} + +// WriteJSONResponse writes headers with the code, encodes resp into w, and logs +// any errors it encounters. r is used to get additional information from the +// request. +func WriteJSONResponse(w http.ResponseWriter, r *http.Request, code int, resp any) { + h := w.Header() + h.Set(httphdr.ContentType, HdrValApplicationJSON) + h.Set(httphdr.Server, UserAgent()) + + w.WriteHeader(code) + + err := json.NewEncoder(w).Encode(resp) + if err != nil { + log.Error("aghhttp: writing json resp to %s %s: %s", r.Method, r.URL.Path, err) + } +} + +// WriteJSONResponseOK writes headers with the code 200 OK, encodes v into w, +// and logs any errors it encounters. r is used to get additional information +// from the request. +func WriteJSONResponseOK(w http.ResponseWriter, r *http.Request, v any) { + WriteJSONResponse(w, r, http.StatusOK, v) +} + +// ErrorCode is the error code as used by the HTTP API. See the ErrorCode +// definition in the OpenAPI specification. +type ErrorCode string + +// ErrorCode constants. +// +// TODO(a.garipov): Expand and document codes. +const ( + // ErrorCodeTMP000 is the temporary error code used for all errors. + ErrorCodeTMP000 = "" +) + +// HTTPAPIErrorResp is the error response as used by the HTTP API. See the +// BadRequestResp, InternalServerErrorResp, and similar objects in the OpenAPI +// specification. +type HTTPAPIErrorResp struct { + Code ErrorCode `json:"code"` + Msg string `json:"msg"` +} + +// WriteJSONResponseError encodes err as a JSON error into w, and logs any +// errors it encounters. r is used to get additional information from the +// request. +func WriteJSONResponseError(w http.ResponseWriter, r *http.Request, err error) { + log.Error("aghhttp: writing json error to %s %s: %s", r.Method, r.URL.Path, err) + + WriteJSONResponse(w, r, http.StatusUnprocessableEntity, &HTTPAPIErrorResp{ + Code: ErrorCodeTMP000, + Msg: err.Error(), + }) +} diff --git a/internal/aghhttp/json_test.go b/internal/aghhttp/json_test.go new file mode 100644 index 00000000..9a2d33f1 --- /dev/null +++ b/internal/aghhttp/json_test.go @@ -0,0 +1,114 @@ +package aghhttp_test + +import ( + "encoding/json" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// testJSONTime is the JSON time for tests. +var testJSONTime = aghhttp.JSONTime(time.Unix(1_234_567_890, 123_456_000).UTC()) + +// testJSONTimeStr is the string with the JSON encoding of testJSONTime. +const testJSONTimeStr = "1234567890123.456" + +func TestJSONTime_MarshalJSON(t *testing.T) { + testCases := []struct { + name string + wantErrMsg string + in aghhttp.JSONTime + want []byte + }{{ + name: "unix_zero", + wantErrMsg: "", + in: aghhttp.JSONTime(time.Unix(0, 0)), + want: []byte("0"), + }, { + name: "empty", + wantErrMsg: "", + in: aghhttp.JSONTime{}, + want: []byte("-6795364578871.345"), + }, { + name: "time", + wantErrMsg: "", + in: testJSONTime, + want: []byte(testJSONTimeStr), + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := tc.in.MarshalJSON() + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + + assert.Equal(t, tc.want, got) + }) + } + + t.Run("json", func(t *testing.T) { + in := &struct { + A aghhttp.JSONTime + }{ + A: testJSONTime, + } + + got, err := json.Marshal(in) + require.NoError(t, err) + + assert.Equal(t, []byte(`{"A":`+testJSONTimeStr+`}`), got) + }) +} + +func TestJSONTime_UnmarshalJSON(t *testing.T) { + testCases := []struct { + name string + wantErrMsg string + want aghhttp.JSONTime + data []byte + }{{ + name: "time", + wantErrMsg: "", + want: testJSONTime, + data: []byte(testJSONTimeStr), + }, { + name: "bad", + wantErrMsg: `parsing json time: strconv.ParseFloat: parsing "{}": ` + + `invalid syntax`, + want: aghhttp.JSONTime{}, + data: []byte(`{}`), + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var got aghhttp.JSONTime + err := got.UnmarshalJSON(tc.data) + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + + assert.Equal(t, tc.want, got) + }) + } + + t.Run("nil", func(t *testing.T) { + err := (*aghhttp.JSONTime)(nil).UnmarshalJSON([]byte("0")) + require.Error(t, err) + + msg := err.Error() + assert.Equal(t, "json time is nil", msg) + }) + + t.Run("json", func(t *testing.T) { + want := testJSONTime + var got struct { + A aghhttp.JSONTime + } + + err := json.Unmarshal([]byte(`{"A":`+testJSONTimeStr+`}`), &got) + require.NoError(t, err) + + assert.Equal(t, want, got.A) + }) +} diff --git a/internal/aghnet/dhcp_unix.go b/internal/aghnet/dhcp_unix.go index cb44e29d..b75f40c4 100644 --- a/internal/aghnet/dhcp_unix.go +++ b/internal/aghnet/dhcp_unix.go @@ -60,7 +60,7 @@ func ifaceIPv4Subnet(iface *net.Interface) (subnet netip.Prefix, err error) { } if ip = ip.To4(); ip != nil { - return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ip)), maskLen), nil + return netip.PrefixFrom(netip.AddrFrom4([4]byte(ip)), maskLen), nil } } diff --git a/internal/aghnet/hostscontainer.go b/internal/aghnet/hostscontainer.go index f2e57c46..0d9a4bcc 100644 --- a/internal/aghnet/hostscontainer.go +++ b/internal/aghnet/hostscontainer.go @@ -1,25 +1,19 @@ package aghnet import ( - "bufio" "fmt" "io" "io/fs" "net/netip" "path" - "strings" - "sync" + "sync/atomic" "github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/hostsfile" "github.com/AdguardTeam/golibs/log" - "github.com/AdguardTeam/golibs/netutil" - "github.com/AdguardTeam/golibs/stringutil" - "github.com/AdguardTeam/urlfilter" - "github.com/AdguardTeam/urlfilter/filterlist" - "github.com/AdguardTeam/urlfilter/rules" - "github.com/miekg/dns" "golang.org/x/exp/maps" + "golang.org/x/exp/slices" ) // DefaultHostsPaths returns the slice of paths default for the operating system @@ -29,95 +23,51 @@ func DefaultHostsPaths() (paths []string) { return defaultHostsPaths() } -// requestMatcher combines the logic for matching requests and translating the -// appropriate rules. -type requestMatcher struct { - // stateLock protects all the fields of requestMatcher. - stateLock *sync.RWMutex - - // rulesStrg stores the rules obtained from the hosts' file. - rulesStrg *filterlist.RuleStorage - // engine serves rulesStrg. - engine *urlfilter.DNSEngine - - // translator maps generated $dnsrewrite rules into hosts-syntax rules. - // - // TODO(e.burkov): Store the filename from which the rule was parsed. - translator map[string]string -} - -// MatchRequest processes the request rewriting hostnames and addresses read -// from the operating system's hosts files. res is nil for any request having -// not an A/AAAA or PTR type, see man 5 hosts. -// -// It's safe for concurrent use. -func (rm *requestMatcher) MatchRequest( - req *urlfilter.DNSRequest, -) (res *urlfilter.DNSResult, ok bool) { - switch req.DNSType { - case dns.TypeA, dns.TypeAAAA, dns.TypePTR: - log.Debug( - "%s: handling %s request for %s", - hostsContainerPrefix, - dns.Type(req.DNSType), - req.Hostname, - ) - - rm.stateLock.RLock() - defer rm.stateLock.RUnlock() - - return rm.engine.MatchRequest(req) - default: - return nil, false +// MatchAddr returns the records for the IP address. +func (hc *HostsContainer) MatchAddr(ip netip.Addr) (recs []*hostsfile.Record) { + cur := hc.current.Load() + if cur == nil { + return nil } + + return cur.addrs[ip] } -// Translate returns the source hosts-syntax rule for the generated dnsrewrite -// rule or an empty string if the last doesn't exist. The returned rules are in -// a processed format like: -// -// ip host1 host2 ... -func (rm *requestMatcher) Translate(rule string) (hostRule string) { - rm.stateLock.RLock() - defer rm.stateLock.RUnlock() +// MatchName returns the records for the hostname. +func (hc *HostsContainer) MatchName(name string) (recs []*hostsfile.Record) { + cur := hc.current.Load() + if cur != nil { + recs = cur.names[name] + } - return rm.translator[rule] -} - -// resetEng updates container's engine and the translation map. -func (rm *requestMatcher) resetEng(rulesStrg *filterlist.RuleStorage, tr map[string]string) { - rm.stateLock.Lock() - defer rm.stateLock.Unlock() - - rm.rulesStrg = rulesStrg - rm.engine = urlfilter.NewDNSEngine(rm.rulesStrg) - - rm.translator = tr + return recs } // hostsContainerPrefix is a prefix for logging and wrapping errors in // HostsContainer's methods. const hostsContainerPrefix = "hosts container" +// Hosts is a map of IP addresses to the records, as it primarily stored in the +// [HostsContainer]. It should not be accessed for writing since it may be read +// concurrently, users should clone it before modifying. +// +// The order of records for each address is preserved from original files, but +// the order of the addresses, being a map key, is not. +// +// TODO(e.burkov): Probably, this should be a sorted slice of records. +type Hosts map[netip.Addr][]*hostsfile.Record + // HostsContainer stores the relevant hosts database provided by the OS and // processes both A/AAAA and PTR DNS requests for those. -// -// TODO(e.burkov): Improve API and move to golibs. type HostsContainer struct { - // requestMatcher matches the requests and translates the rules. It's - // embedded to implement MatchRequest and Translate for *HostsContainer. - // - // TODO(a.garipov, e.burkov): Consider fully merging into HostsContainer. - requestMatcher - // done is the channel to sign closing the container. done chan struct{} // updates is the channel for receiving updated hosts. - updates chan HostsRecords + updates chan Hosts - // last is the set of hosts that was cached within last detected change. - last HostsRecords + // current is the last set of hosts parsed. + current atomic.Pointer[hostsIndex] // fsys is the working file system to read hosts files from. fsys fs.FS @@ -127,30 +77,6 @@ type HostsContainer struct { // patterns stores specified paths in the fs.Glob-compatible form. patterns []string - - // listID is the identifier for the list of generated rules. - listID int -} - -// HostsRecords is a mapping of an IP address to its hosts data. -type HostsRecords map[netip.Addr]*HostsRecord - -// HostsRecord represents a single hosts file record. -type HostsRecord struct { - Aliases *stringutil.Set - Canonical string -} - -// Equal returns true if all fields of rec are equal to field in other or they -// both are nil. -func (rec *HostsRecord) Equal(other *HostsRecord) (ok bool) { - if rec == nil { - return other == nil - } else if other == nil { - return false - } - - return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases) } // ErrNoHostsPaths is returned when there are no valid paths to watch passed to @@ -162,7 +88,6 @@ const ErrNoHostsPaths errors.Error = "no valid paths to hosts files provided" // shouldn't be empty and each of paths should locate either a file or a // directory in fsys. fsys and w must be non-nil. func NewHostsContainer( - listID int, fsys fs.FS, w aghos.FSWatcher, paths ...string, @@ -182,12 +107,8 @@ func NewHostsContainer( } hc = &HostsContainer{ - requestMatcher: requestMatcher{ - stateLock: &sync.RWMutex{}, - }, - listID: listID, done: make(chan struct{}, 1), - updates: make(chan HostsRecords, 1), + updates: make(chan Hosts, 1), fsys: fsys, watcher: w, patterns: patterns, @@ -233,7 +154,7 @@ func (hc *HostsContainer) Close() (err error) { } // Upd returns the channel into which the updates are sent. -func (hc *HostsContainer) Upd() (updates <-chan HostsRecords) { +func (hc *HostsContainer) Upd() (updates <-chan Hosts) { return hc.updates } @@ -280,7 +201,7 @@ func (hc *HostsContainer) handleEvents() { } if err := hc.refresh(); err != nil { - log.Error("%s: %s", hostsContainerPrefix, err) + log.Error("%s: warning: refreshing: %s", hostsContainerPrefix, err) } case _, ok = <-hc.done: // Go on. @@ -288,198 +209,83 @@ func (hc *HostsContainer) handleEvents() { } } -// hostsParser is a helper type to parse rules from the operating system's hosts -// file. It exists for only a single refreshing session. -type hostsParser struct { - // rulesBuilder builds the resulting rules list content. - rulesBuilder *strings.Builder - - // translations maps generated rules into actual hosts file lines. - translations map[string]string - - // table stores only the unique IP-hostname pairs. It's also sent to the - // updates channel afterwards. - table HostsRecords -} - -// newHostsParser creates a new *hostsParser with buffers of size taken from the -// previous parse. -func (hc *HostsContainer) newHostsParser() (hp *hostsParser) { - return &hostsParser{ - rulesBuilder: &strings.Builder{}, - translations: map[string]string{}, - table: make(HostsRecords, len(hc.last)), - } -} - -// parseFile is a aghos.FileWalker for parsing the files with hosts syntax. It -// never signs to stop walking and never returns any additional patterns. -// -// See man hosts(5). -func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err error) { - s := bufio.NewScanner(r) - for s.Scan() { - ip, hosts := hp.parseLine(s.Text()) - if ip == (netip.Addr{}) || len(hosts) == 0 { - continue - } - - hp.addRecord(ip, hosts) - } - - return nil, true, s.Err() -} - -// parseLine parses the line having the hosts syntax ignoring invalid ones. -func (hp *hostsParser) parseLine(line string) (ip netip.Addr, hosts []string) { - fields := strings.Fields(line) - if len(fields) < 2 { - return netip.Addr{}, nil - } - - ip, err := netip.ParseAddr(fields[0]) - if err != nil { - return netip.Addr{}, nil - } - - for _, f := range fields[1:] { - hashIdx := strings.IndexByte(f, '#') - if hashIdx == 0 { - // The rest of the fields are a part of the comment so return. - break - } else if hashIdx > 0 { - // Only a part of the field is a comment. - f = f[:hashIdx] - } - - // Make sure that invalid hosts aren't turned into rules. - // - // See https://github.com/AdguardTeam/AdGuardHome/issues/3946. - // - // TODO(e.burkov): Investigate if hosts may contain DNS-SD domains. - err = netutil.ValidateHostname(f) - if err != nil { - log.Error("%s: host %q is invalid, ignoring", hostsContainerPrefix, f) - - continue - } - - hosts = append(hosts, f) - } - - return ip, hosts -} - -// addRecord puts the record for the IP address to the rules builder if needed. -// The first host is considered to be the canonical name for the IP address. -// hosts must have at least one name. -func (hp *hostsParser) addRecord(ip netip.Addr, hosts []string) { - line := strings.Join(append([]string{ip.String()}, hosts...), " ") - - rec, ok := hp.table[ip] - if !ok { - rec = &HostsRecord{ - Aliases: stringutil.NewSet(), - } - - rec.Canonical, hosts = hosts[0], hosts[1:] - hp.addRules(ip, rec.Canonical, line) - hp.table[ip] = rec - } - - for _, host := range hosts { - if rec.Canonical == host || rec.Aliases.Has(host) { - continue - } - - rec.Aliases.Add(host) - - hp.addRules(ip, host, line) - } -} - -// addRules adds rules and rule translations for the line. -func (hp *hostsParser) addRules(ip netip.Addr, host, line string) { - rule, rulePtr := hp.writeRules(host, ip) - hp.translations[rule], hp.translations[rulePtr] = line, line - - log.Debug("%s: added ip-host pair %q-%q", hostsContainerPrefix, ip, host) -} - -// writeRules writes the actual rule for the qtype and the PTR for the host-ip -// pair into internal builders. -func (hp *hostsParser) writeRules(host string, ip netip.Addr) (rule, rulePtr string) { - // TODO(a.garipov): Add a netip.Addr version to netutil. - arpa, err := netutil.IPToReversedAddr(ip.AsSlice()) - if err != nil { - return "", "" - } - - const ( - nl = "\n" - - rwSuccess = "^$dnsrewrite=NOERROR;" - rwSuccessPTR = "^$dnsrewrite=NOERROR;PTR;" - - modLen = len(rules.MaskPipe) + len(rwSuccess) + len(";") - modLenPTR = len(rules.MaskPipe) + len(rwSuccessPTR) - ) - - var qtype string - // The validation of the IP address has been performed earlier so it is - // guaranteed to be either an IPv4 or an IPv6. - if ip.Is4() { - qtype = "A" - } else { - qtype = "AAAA" - } - - ipStr := ip.String() - fqdn := dns.Fqdn(host) - - ruleBuilder := &strings.Builder{} - ruleBuilder.Grow(modLen + len(host) + len(qtype) + len(ipStr)) - stringutil.WriteToBuilder(ruleBuilder, rules.MaskPipe, host, rwSuccess, qtype, ";", ipStr) - rule = ruleBuilder.String() - - ruleBuilder.Reset() - - ruleBuilder.Grow(modLenPTR + len(arpa) + len(fqdn)) - stringutil.WriteToBuilder(ruleBuilder, rules.MaskPipe, arpa, rwSuccessPTR, fqdn) - - rulePtr = ruleBuilder.String() - - hp.rulesBuilder.Grow(len(rule) + len(rulePtr) + 2*len(nl)) - stringutil.WriteToBuilder(hp.rulesBuilder, rule, nl, rulePtr, nl) - - return rule, rulePtr -} - // sendUpd tries to send the parsed data to the ch. -func (hp *hostsParser) sendUpd(ch chan HostsRecords) { +func (hc *HostsContainer) sendUpd(recs Hosts) { log.Debug("%s: sending upd", hostsContainerPrefix) - upd := hp.table + ch := hc.updates select { - case ch <- upd: + case ch <- recs: // Updates are delivered. Go on. case <-ch: - ch <- upd + ch <- recs log.Debug("%s: replaced the last update", hostsContainerPrefix) - case ch <- upd: + case ch <- recs: // The previous update was just read and the next one pushed. Go on. default: log.Error("%s: the updates channel is broken", hostsContainerPrefix) } } -// newStrg creates a new rules storage from parsed data. -func (hp *hostsParser) newStrg(id int) (s *filterlist.RuleStorage, err error) { - return filterlist.NewRuleStorage([]filterlist.RuleList{&filterlist.StringRuleList{ - ID: id, - RulesText: hp.rulesBuilder.String(), - IgnoreCosmetic: true, - }}) +// hostsIndex is a [hostsfile.Set] to enumerate all the records. +type hostsIndex struct { + // addrs maps IP addresses to the records. + addrs Hosts + + // names maps hostnames to the records. + names map[string][]*hostsfile.Record +} + +// walk is a file walking function for hostsIndex. +func (idx *hostsIndex) walk(r io.Reader) (patterns []string, cont bool, err error) { + return nil, true, hostsfile.Parse(idx, r, nil) +} + +// type check +var _ hostsfile.Set = (*hostsIndex)(nil) + +// Add implements the [hostsfile.Set] interface for *hostsIndex. +func (idx *hostsIndex) Add(rec *hostsfile.Record) { + idx.addrs[rec.Addr] = append(idx.addrs[rec.Addr], rec) + for _, name := range rec.Names { + idx.names[name] = append(idx.names[name], rec) + } +} + +// type check +var _ hostsfile.HandleSet = (*hostsIndex)(nil) + +// HandleInvalid implements the [hostsfile.HandleSet] interface for *hostsIndex. +func (idx *hostsIndex) HandleInvalid(src string, _ []byte, err error) { + lineErr := &hostsfile.LineError{} + if !errors.As(err, &lineErr) { + // Must not happen if idx passed to [hostsfile.Parse]. + return + } else if errors.Is(lineErr, hostsfile.ErrEmptyLine) { + // Ignore empty lines. + return + } + + log.Info("%s: warning: parsing %q: %s", hostsContainerPrefix, src, lineErr) +} + +// equalRecs is an equality function for [*hostsfile.Record]. +func equalRecs(a, b *hostsfile.Record) (ok bool) { + return a.Addr == b.Addr && a.Source == b.Source && slices.Equal(a.Names, b.Names) +} + +// equalRecSlices is an equality function for slices of [*hostsfile.Record]. +func equalRecSlices(a, b []*hostsfile.Record) (ok bool) { return slices.EqualFunc(a, b, equalRecs) } + +// Equal returns true if indexes are equal. +func (idx *hostsIndex) Equal(other *hostsIndex) (ok bool) { + if idx == nil { + return other == nil + } else if other == nil { + return false + } + + return maps.EqualFunc(idx.addrs, other.addrs, equalRecSlices) } // refresh gets the data from specified files and propagates the updates if @@ -489,27 +295,27 @@ func (hp *hostsParser) newStrg(id int) (s *filterlist.RuleStorage, err error) { func (hc *HostsContainer) refresh() (err error) { log.Debug("%s: refreshing", hostsContainerPrefix) - hp := hc.newHostsParser() - if _, err = aghos.FileWalker(hp.parseFile).Walk(hc.fsys, hc.patterns...); err != nil { - return fmt.Errorf("refreshing : %w", err) + var addrLen, nameLen int + last := hc.current.Load() + if last != nil { + addrLen, nameLen = len(last.addrs), len(last.names) + } + idx := &hostsIndex{ + addrs: make(Hosts, addrLen), + names: make(map[string][]*hostsfile.Record, nameLen), } - // hc.last is nil on the first refresh, so let that one through. - if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).Equal) { - log.Debug("%s: no changes detected", hostsContainerPrefix) - - return nil - } - defer hp.sendUpd(hc.updates) - - hc.last = maps.Clone(hp.table) - - var rulesStrg *filterlist.RuleStorage - if rulesStrg, err = hp.newStrg(hc.listID); err != nil { - return fmt.Errorf("initializing rules storage: %w", err) + _, err = aghos.FileWalker(idx.walk).Walk(hc.fsys, hc.patterns...) + if err != nil { + // Don't wrap the error since it's informative enough as is. + return err } - hc.resetEng(rulesStrg, hp.translations) + // TODO(e.burkov): Serialize updates using time. + if !last.Equal(idx) { + hc.current.Store(idx) + hc.sendUpd(idx.addrs) + } return nil } diff --git a/internal/aghnet/hostscontainer_internal_test.go b/internal/aghnet/hostscontainer_internal_test.go index e3855f39..321c3dec 100644 --- a/internal/aghnet/hostscontainer_internal_test.go +++ b/internal/aghnet/hostscontainer_internal_test.go @@ -2,13 +2,11 @@ package aghnet import ( "io/fs" - "net/netip" "path" "testing" "testing/fstest" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil/fakefs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -76,69 +74,3 @@ func TestHostsContainer_PathsToPatterns(t *testing.T) { assert.ErrorIs(t, err, errStat) }) } - -func TestUniqueRules_ParseLine(t *testing.T) { - ip := netutil.IPv4Localhost() - ipStr := ip.String() - - testCases := []struct { - name string - line string - wantIP netip.Addr - wantHosts []string - }{{ - name: "simple", - line: ipStr + ` hostname`, - wantIP: ip, - wantHosts: []string{"hostname"}, - }, { - name: "aliases", - line: ipStr + ` hostname alias`, - wantIP: ip, - wantHosts: []string{"hostname", "alias"}, - }, { - name: "invalid_line", - line: ipStr, - wantIP: netip.Addr{}, - wantHosts: nil, - }, { - name: "invalid_line_hostname", - line: ipStr + ` # hostname`, - wantIP: ip, - wantHosts: nil, - }, { - name: "commented_aliases", - line: ipStr + ` hostname # alias`, - wantIP: ip, - wantHosts: []string{"hostname"}, - }, { - name: "whole_comment", - line: `# ` + ipStr + ` hostname`, - wantIP: netip.Addr{}, - wantHosts: nil, - }, { - name: "partial_comment", - line: ipStr + ` host#name`, - wantIP: ip, - wantHosts: []string{"host"}, - }, { - name: "empty", - line: ``, - wantIP: netip.Addr{}, - wantHosts: nil, - }, { - name: "bad_hosts", - line: ipStr + ` bad..host bad._tld empty.tld. ok.host`, - wantIP: ip, - wantHosts: []string{"ok.host"}, - }} - - for _, tc := range testCases { - hp := hostsParser{} - t.Run(tc.name, func(t *testing.T) { - got, hosts := hp.parseLine(tc.line) - assert.Equal(t, tc.wantIP, got) - assert.Equal(t, tc.wantHosts, hosts) - }) - } -} diff --git a/internal/aghnet/hostscontainer_test.go b/internal/aghnet/hostscontainer_test.go index 00c2aeed..ac30777f 100644 --- a/internal/aghnet/hostscontainer_test.go +++ b/internal/aghnet/hostscontainer_test.go @@ -1,9 +1,9 @@ package aghnet_test import ( - "net" + "net/netip" "path" - "strings" + "path/filepath" "sync/atomic" "testing" "testing/fstest" @@ -13,18 +13,146 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/hostsfile" "github.com/AdguardTeam/golibs/netutil" - "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/testutil" - "github.com/AdguardTeam/urlfilter" - "github.com/AdguardTeam/urlfilter/rules" - "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// nl is a newline character. const nl = "\n" +// Variables mirroring the etc_hosts file from testdata. +var ( + addr1000 = netip.MustParseAddr("1.0.0.0") + addr1001 = netip.MustParseAddr("1.0.0.1") + addr1002 = netip.MustParseAddr("1.0.0.2") + addr1003 = netip.MustParseAddr("1.0.0.3") + addr1004 = netip.MustParseAddr("1.0.0.4") + addr1357 = netip.MustParseAddr("1.3.5.7") + addr4216 = netip.MustParseAddr("4.2.1.6") + addr7531 = netip.MustParseAddr("7.5.3.1") + + addr0 = netip.MustParseAddr("::") + addr1 = netip.MustParseAddr("::1") + addr2 = netip.MustParseAddr("::2") + addr3 = netip.MustParseAddr("::3") + addr4 = netip.MustParseAddr("::4") + addr42 = netip.MustParseAddr("::42") + addr13 = netip.MustParseAddr("::13") + addr31 = netip.MustParseAddr("::31") + + hostsSrc = "./" + filepath.Join("./testdata", "etc_hosts") + + testHosts = map[netip.Addr][]*hostsfile.Record{ + addr1000: {{ + Addr: addr1000, + Source: hostsSrc, + Names: []string{"hello", "hello.world"}, + }, { + Addr: addr1000, + Source: hostsSrc, + Names: []string{"hello.world.again"}, + }, { + Addr: addr1000, + Source: hostsSrc, + Names: []string{"hello.world"}, + }}, + addr1001: {{ + Addr: addr1001, + Source: hostsSrc, + Names: []string{"simplehost"}, + }, { + Addr: addr1001, + Source: hostsSrc, + Names: []string{"simplehost"}, + }}, + addr1002: {{ + Addr: addr1002, + Source: hostsSrc, + Names: []string{"a.whole", "lot.of", "aliases", "for.testing"}, + }}, + addr1003: {{ + Addr: addr1003, + Source: hostsSrc, + Names: []string{"*"}, + }}, + addr1004: {{ + Addr: addr1004, + Source: hostsSrc, + Names: []string{"*.com"}, + }}, + addr1357: {{ + Addr: addr1357, + Source: hostsSrc, + Names: []string{"domain4", "domain4.alias"}, + }}, + addr7531: {{ + Addr: addr7531, + Source: hostsSrc, + Names: []string{"domain4.alias", "domain4"}, + }}, + addr4216: {{ + Addr: addr4216, + Source: hostsSrc, + Names: []string{"domain", "domain.alias"}, + }}, + addr0: {{ + Addr: addr0, + Source: hostsSrc, + Names: []string{"hello", "hello.world"}, + }, { + Addr: addr0, + Source: hostsSrc, + Names: []string{"hello.world.again"}, + }, { + Addr: addr0, + Source: hostsSrc, + Names: []string{"hello.world"}, + }}, + addr1: {{ + Addr: addr1, + Source: hostsSrc, + Names: []string{"simplehost"}, + }, { + Addr: addr1, + Source: hostsSrc, + Names: []string{"simplehost"}, + }}, + addr2: {{ + Addr: addr2, + Source: hostsSrc, + Names: []string{"a.whole", "lot.of", "aliases", "for.testing"}, + }}, + addr3: {{ + Addr: addr3, + Source: hostsSrc, + Names: []string{"*"}, + }}, + addr4: {{ + Addr: addr4, + Source: hostsSrc, + Names: []string{"*.com"}, + }}, + addr42: {{ + Addr: addr42, + Source: hostsSrc, + Names: []string{"domain.alias", "domain"}, + }}, + addr13: {{ + Addr: addr13, + Source: hostsSrc, + Names: []string{"domain6", "domain6.alias"}, + }}, + addr31: {{ + Addr: addr31, + Source: hostsSrc, + Names: []string{"domain6.alias", "domain6"}, + }}, + } +) + func TestNewHostsContainer(t *testing.T) { const dirname = "dir" const filename = "file1" @@ -73,7 +201,7 @@ func TestNewHostsContainer(t *testing.T) { return eventsCh } - hc, err := aghnet.NewHostsContainer(0, testFS, &aghtest.FSWatcher{ + hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{ OnEvents: onEvents, OnAdd: onAdd, OnClose: func() (err error) { return nil }, @@ -99,7 +227,7 @@ func TestNewHostsContainer(t *testing.T) { t.Run("nil_fs", func(t *testing.T) { require.Panics(t, func() { - _, _ = aghnet.NewHostsContainer(0, nil, &aghtest.FSWatcher{ + _, _ = aghnet.NewHostsContainer(nil, &aghtest.FSWatcher{ // Those shouldn't panic. OnEvents: func() (e <-chan struct{}) { return nil }, OnAdd: func(name string) (err error) { return nil }, @@ -110,7 +238,7 @@ func TestNewHostsContainer(t *testing.T) { t.Run("nil_watcher", func(t *testing.T) { require.Panics(t, func() { - _, _ = aghnet.NewHostsContainer(0, testFS, nil, p) + _, _ = aghnet.NewHostsContainer(testFS, nil, p) }) }) @@ -123,7 +251,7 @@ func TestNewHostsContainer(t *testing.T) { OnClose: func() (err error) { return nil }, } - hc, err := aghnet.NewHostsContainer(0, testFS, errWatcher, p) + hc, err := aghnet.NewHostsContainer(testFS, errWatcher, p) require.ErrorIs(t, err, errOnAdd) assert.Nil(t, hc) @@ -136,6 +264,9 @@ func TestHostsContainer_refresh(t *testing.T) { ip := netutil.IPv4Localhost() ipStr := ip.String() + anotherIPStr := "1.2.3.4" + anotherIP := netip.MustParseAddr(anotherIPStr) + testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}} // event is a convenient alias for an empty struct{} to emit test events. @@ -154,40 +285,44 @@ func TestHostsContainer_refresh(t *testing.T) { OnClose: func() (err error) { return nil }, } - hc, err := aghnet.NewHostsContainer(0, testFS, w, "dir") + hc, err := aghnet.NewHostsContainer(testFS, w, "dir") require.NoError(t, err) testutil.CleanupAndRequireSuccess(t, hc.Close) - checkRefresh := func(t *testing.T, want *aghnet.HostsRecord) { + checkRefresh := func(t *testing.T, want aghnet.Hosts) { t.Helper() upd, ok := aghchan.MustReceive(hc.Upd(), 1*time.Second) require.True(t, ok) - require.NotNil(t, upd) - assert.Len(t, upd, 1) - - rec, ok := upd[ip] - require.True(t, ok) - require.NotNil(t, rec) - - assert.Truef(t, rec.Equal(want), "%+v != %+v", rec, want) + assert.Equal(t, want, upd) } t.Run("initial_refresh", func(t *testing.T) { - checkRefresh(t, &aghnet.HostsRecord{ - Aliases: stringutil.NewSet(), - Canonical: "hostname", + checkRefresh(t, aghnet.Hosts{ + ip: {{ + Addr: ip, + Source: "file1", + Names: []string{"hostname"}, + }}, }) }) t.Run("second_refresh", func(t *testing.T) { - testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)} + testFS["dir/file2"] = &fstest.MapFile{Data: []byte(anotherIPStr + ` alias` + nl)} eventsCh <- event{} - checkRefresh(t, &aghnet.HostsRecord{ - Aliases: stringutil.NewSet("alias"), - Canonical: "hostname", + checkRefresh(t, aghnet.Hosts{ + ip: {{ + Addr: ip, + Source: "file1", + Names: []string{"hostname"}, + }}, + anotherIP: {{ + Addr: anotherIP, + Source: "file2", + Names: []string{"alias"}, + }}, }) }) @@ -198,12 +333,9 @@ func TestHostsContainer_refresh(t *testing.T) { // Require the changes are written. require.Eventually(t, func() bool { - res, ok := hc.MatchRequest(&urlfilter.DNSRequest{ - Hostname: "hostname", - DNSType: dns.TypeA, - }) + ips := hc.MatchName("hostname") - return !ok && res.DNSRewrites() == nil + return len(ips) == 0 }, 5*time.Second, time.Second/2) // Make a change again. @@ -212,285 +344,117 @@ func TestHostsContainer_refresh(t *testing.T) { // Require the changes are written. require.Eventually(t, func() bool { - res, ok := hc.MatchRequest(&urlfilter.DNSRequest{ - Hostname: "hostname", - DNSType: dns.TypeA, - }) + ips := hc.MatchName("hostname") - return !ok && res.DNSRewrites() != nil + return len(ips) > 0 }, 5*time.Second, time.Second/2) assert.Len(t, hc.Upd(), 1) }) } -func TestHostsContainer_Translate(t *testing.T) { +func TestHostsContainer_MatchName(t *testing.T) { + require.NoError(t, fstest.TestFS(testdata, "etc_hosts")) + stubWatcher := aghtest.FSWatcher{ OnEvents: func() (e <-chan struct{}) { return nil }, OnAdd: func(name string) (err error) { return nil }, OnClose: func() (err error) { return nil }, } - require.NoError(t, fstest.TestFS(testdata, "etc_hosts")) - - hc, err := aghnet.NewHostsContainer(0, testdata, &stubWatcher, "etc_hosts") - require.NoError(t, err) - testutil.CleanupAndRequireSuccess(t, hc.Close) - testCases := []struct { - name string - rule string - wantTrans []string - }{{ - name: "simplehost", - rule: "|simplehost^$dnsrewrite=NOERROR;A;1.0.0.1", - wantTrans: []string{"1.0.0.1", "simplehost"}, - }, { - name: "hello", - rule: "|hello^$dnsrewrite=NOERROR;A;1.0.0.0", - wantTrans: []string{"1.0.0.0", "hello", "hello.world"}, - }, { - name: "hello-alias", - rule: "|hello.world.again^$dnsrewrite=NOERROR;A;1.0.0.0", - wantTrans: []string{"1.0.0.0", "hello.world.again"}, - }, { - name: "simplehost_v6", - rule: "|simplehost^$dnsrewrite=NOERROR;AAAA;::1", - wantTrans: []string{"::1", "simplehost"}, - }, { - name: "hello_v6", - rule: "|hello^$dnsrewrite=NOERROR;AAAA;::", - wantTrans: []string{"::", "hello", "hello.world"}, - }, { - name: "hello_v6-alias", - rule: "|hello.world.again^$dnsrewrite=NOERROR;AAAA;::", - wantTrans: []string{"::", "hello.world.again"}, - }, { - name: "simplehost_ptr", - rule: "|1.0.0.1.in-addr.arpa^$dnsrewrite=NOERROR;PTR;simplehost.", - wantTrans: []string{"1.0.0.1", "simplehost"}, - }, { - name: "hello_ptr", - rule: "|0.0.0.1.in-addr.arpa^$dnsrewrite=NOERROR;PTR;hello.", - wantTrans: []string{"1.0.0.0", "hello", "hello.world"}, - }, { - name: "hello_ptr-alias", - rule: "|0.0.0.1.in-addr.arpa^$dnsrewrite=NOERROR;PTR;hello.world.again.", - wantTrans: []string{"1.0.0.0", "hello.world.again"}, - }, { - name: "simplehost_ptr_v6", - rule: "|1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" + - "^$dnsrewrite=NOERROR;PTR;simplehost.", - wantTrans: []string{"::1", "simplehost"}, - }, { - name: "hello_ptr_v6", - rule: "|0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" + - "^$dnsrewrite=NOERROR;PTR;hello.", - wantTrans: []string{"::", "hello", "hello.world"}, - }, { - name: "hello_ptr_v6-alias", - rule: "|0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" + - "^$dnsrewrite=NOERROR;PTR;hello.world.again.", - wantTrans: []string{"::", "hello.world.again"}, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got := stringutil.NewSet(strings.Fields(hc.Translate(tc.rule))...) - assert.True(t, stringutil.NewSet(tc.wantTrans...).Equal(got)) - }) - } -} - -func TestHostsContainer(t *testing.T) { - const listID = 1234 - - require.NoError(t, fstest.TestFS(testdata, "etc_hosts")) - - testCases := []struct { - req *urlfilter.DNSRequest + req string name string - want []*rules.DNSRewrite + want []*hostsfile.Record }{{ - req: &urlfilter.DNSRequest{ - Hostname: "simplehost", - DNSType: dns.TypeA, - }, + req: "simplehost", name: "simple", - want: []*rules.DNSRewrite{{ - RCode: dns.RcodeSuccess, - Value: net.IPv4(1, 0, 0, 1), - RRType: dns.TypeA, - }, { - RCode: dns.RcodeSuccess, - Value: net.ParseIP("::1"), - RRType: dns.TypeAAAA, - }}, + want: append(testHosts[addr1001], testHosts[addr1]...), }, { - req: &urlfilter.DNSRequest{ - Hostname: "hello.world", - DNSType: dns.TypeA, - }, + req: "hello.world", name: "hello_alias", - want: []*rules.DNSRewrite{{ - RCode: dns.RcodeSuccess, - Value: net.IPv4(1, 0, 0, 0), - RRType: dns.TypeA, - }, { - RCode: dns.RcodeSuccess, - Value: net.ParseIP("::"), - RRType: dns.TypeAAAA, - }}, - }, { - req: &urlfilter.DNSRequest{ - Hostname: "hello.world.again", - DNSType: dns.TypeA, + want: []*hostsfile.Record{ + testHosts[addr1000][0], + testHosts[addr1000][2], + testHosts[addr0][0], + testHosts[addr0][2], }, + }, { + req: "hello.world.again", name: "other_line_alias", - want: []*rules.DNSRewrite{{ - RCode: dns.RcodeSuccess, - Value: net.IPv4(1, 0, 0, 0), - RRType: dns.TypeA, - }, { - RCode: dns.RcodeSuccess, - Value: net.ParseIP("::"), - RRType: dns.TypeAAAA, - }}, - }, { - req: &urlfilter.DNSRequest{ - Hostname: "say.hello", - DNSType: dns.TypeA, + want: []*hostsfile.Record{ + testHosts[addr1000][1], + testHosts[addr0][1], }, + }, { + req: "say.hello", name: "hello_subdomain", - want: []*rules.DNSRewrite{}, - }, { - req: &urlfilter.DNSRequest{ - Hostname: "say.hello.world", - DNSType: dns.TypeA, - }, - name: "hello_alias_subdomain", - want: []*rules.DNSRewrite{}, - }, { - req: &urlfilter.DNSRequest{ - Hostname: "for.testing", - DNSType: dns.TypeA, - }, - name: "lots_of_aliases", - want: []*rules.DNSRewrite{{ - RCode: dns.RcodeSuccess, - RRType: dns.TypeA, - Value: net.IPv4(1, 0, 0, 2), - }, { - RCode: dns.RcodeSuccess, - RRType: dns.TypeAAAA, - Value: net.ParseIP("::2"), - }}, - }, { - req: &urlfilter.DNSRequest{ - Hostname: "1.0.0.1.in-addr.arpa", - DNSType: dns.TypePTR, - }, - name: "reverse", - want: []*rules.DNSRewrite{{ - RCode: dns.RcodeSuccess, - RRType: dns.TypePTR, - Value: "simplehost.", - }}, - }, { - req: &urlfilter.DNSRequest{ - Hostname: "nonexistent.example", - DNSType: dns.TypeA, - }, - name: "non-existing", - want: []*rules.DNSRewrite{}, - }, { - req: &urlfilter.DNSRequest{ - Hostname: "1.0.0.1.in-addr.arpa", - DNSType: dns.TypeSRV, - }, - name: "bad_type", want: nil, }, { - req: &urlfilter.DNSRequest{ - Hostname: "domain", - DNSType: dns.TypeA, - }, + req: "say.hello.world", + name: "hello_alias_subdomain", + want: nil, + }, { + req: "for.testing", + name: "lots_of_aliases", + want: append(testHosts[addr1002], testHosts[addr2]...), + }, { + req: "nonexistent.example", + name: "non-existing", + want: nil, + }, { + req: "domain", name: "issue_4216_4_6", - want: []*rules.DNSRewrite{{ - RCode: dns.RcodeSuccess, - RRType: dns.TypeA, - Value: net.IPv4(4, 2, 1, 6), - }, { - RCode: dns.RcodeSuccess, - RRType: dns.TypeAAAA, - Value: net.ParseIP("::42"), - }}, + want: append(testHosts[addr4216], testHosts[addr42]...), }, { - req: &urlfilter.DNSRequest{ - Hostname: "domain4", - DNSType: dns.TypeA, - }, + req: "domain4", name: "issue_4216_4", - want: []*rules.DNSRewrite{{ - RCode: dns.RcodeSuccess, - RRType: dns.TypeA, - Value: net.IPv4(7, 5, 3, 1), - }, { - RCode: dns.RcodeSuccess, - RRType: dns.TypeA, - Value: net.IPv4(1, 3, 5, 7), - }}, + want: append(testHosts[addr1357], testHosts[addr7531]...), }, { - req: &urlfilter.DNSRequest{ - Hostname: "domain6", - DNSType: dns.TypeAAAA, - }, + req: "domain6", name: "issue_4216_6", - want: []*rules.DNSRewrite{{ - RCode: dns.RcodeSuccess, - RRType: dns.TypeAAAA, - Value: net.ParseIP("::13"), - }, { - RCode: dns.RcodeSuccess, - RRType: dns.TypeAAAA, - Value: net.ParseIP("::31"), - }}, + want: append(testHosts[addr13], testHosts[addr31]...), }} + hc, err := aghnet.NewHostsContainer(testdata, &stubWatcher, "etc_hosts") + require.NoError(t, err) + testutil.CleanupAndRequireSuccess(t, hc.Close) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + recs := hc.MatchName(tc.req) + assert.Equal(t, tc.want, recs) + }) + } +} + +func TestHostsContainer_MatchAddr(t *testing.T) { + require.NoError(t, fstest.TestFS(testdata, "etc_hosts")) + stubWatcher := aghtest.FSWatcher{ OnEvents: func() (e <-chan struct{}) { return nil }, OnAdd: func(name string) (err error) { return nil }, OnClose: func() (err error) { return nil }, } - hc, err := aghnet.NewHostsContainer(listID, testdata, &stubWatcher, "etc_hosts") + hc, err := aghnet.NewHostsContainer(testdata, &stubWatcher, "etc_hosts") require.NoError(t, err) testutil.CleanupAndRequireSuccess(t, hc.Close) + testCases := []struct { + req netip.Addr + name string + want []*hostsfile.Record + }{{ + req: netip.AddrFrom4([4]byte{1, 0, 0, 1}), + name: "reverse", + want: testHosts[addr1001], + }} + for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - res, ok := hc.MatchRequest(tc.req) - require.False(t, ok) - - if tc.want == nil { - assert.Nil(t, res) - - return - } - - require.NotNil(t, res) - - rewrites := res.DNSRewrites() - require.Len(t, rewrites, len(tc.want)) - - for i, rewrite := range rewrites { - require.Equal(t, listID, rewrite.FilterListID) - - rw := rewrite.DNSRewrite - require.NotNil(t, rw) - - assert.Equal(t, tc.want[i], rw) - } + recs := hc.MatchAddr(tc.req) + assert.Equal(t, tc.want, recs) }) } } diff --git a/internal/aghnet/ignore.go b/internal/aghnet/ignore.go new file mode 100644 index 00000000..69146800 --- /dev/null +++ b/internal/aghnet/ignore.go @@ -0,0 +1,56 @@ +package aghnet + +import ( + "strings" + + "github.com/AdguardTeam/urlfilter" + "github.com/AdguardTeam/urlfilter/filterlist" + "golang.org/x/exp/slices" +) + +// IgnoreEngine contains the list of rules for ignoring hostnames and matches +// them. +// +// TODO(s.chzhen): Move all urlfilter stuff to aghfilter. +type IgnoreEngine struct { + // engine is the filtering engine that can match rules for ignoring + // hostnames. + engine *urlfilter.DNSEngine + + // ignored is the list of rules for ignoring hostnames. + ignored []string +} + +// NewIgnoreEngine creates a new instance of the IgnoreEngine and stores the +// list of rules for ignoring hostnames. +func NewIgnoreEngine(ignored []string) (e *IgnoreEngine, err error) { + ruleList := &filterlist.StringRuleList{ + RulesText: strings.ToLower(strings.Join(ignored, "\n")), + IgnoreCosmetic: true, + } + ruleStorage, err := filterlist.NewRuleStorage([]filterlist.RuleList{ruleList}) + if err != nil { + return nil, err + } + + return &IgnoreEngine{ + engine: urlfilter.NewDNSEngine(ruleStorage), + ignored: ignored, + }, nil +} + +// Has returns true if IgnoreEngine matches the host. +func (e *IgnoreEngine) Has(host string) (ignore bool) { + if e == nil { + return false + } + + _, ignore = e.engine.Match(host) + + return ignore +} + +// Values returns a copy of list of rules for ignoring hostnames. +func (e *IgnoreEngine) Values() (ignored []string) { + return slices.Clone(e.ignored) +} diff --git a/internal/aghnet/ignore_test.go b/internal/aghnet/ignore_test.go new file mode 100644 index 00000000..0cf936d3 --- /dev/null +++ b/internal/aghnet/ignore_test.go @@ -0,0 +1,46 @@ +package aghnet_test + +import ( + "testing" + + "github.com/AdguardTeam/AdGuardHome/internal/aghnet" + "github.com/stretchr/testify/require" +) + +func TestIgnoreEngine_Has(t *testing.T) { + hostnames := []string{ + "*.example.com", + "example.com", + "|.^", + } + + engine, err := aghnet.NewIgnoreEngine(hostnames) + require.NotNil(t, engine) + require.NoError(t, err) + + testCases := []struct { + name string + host string + ignore bool + }{{ + name: "basic", + host: "example.com", + ignore: true, + }, { + name: "root", + host: ".", + ignore: true, + }, { + name: "wildcard", + host: "www.example.com", + ignore: true, + }, { + name: "not_ignored", + host: "something.com", + ignore: false, + }} + + for _, tc := range testCases { + require.Equal(t, tc.ignore, engine.Has(tc.host)) + } +} diff --git a/internal/aghnet/interfaces_unix.go b/internal/aghnet/interfaces_bsd.go similarity index 76% rename from internal/aghnet/interfaces_unix.go rename to internal/aghnet/interfaces_bsd.go index 476f1a1e..dcff75b8 100644 --- a/internal/aghnet/interfaces_unix.go +++ b/internal/aghnet/interfaces_bsd.go @@ -4,7 +4,6 @@ package aghnet import ( "context" - "fmt" "net" "os" "syscall" @@ -24,20 +23,9 @@ func reuseAddrCtrl(_, _ string, c syscall.RawConn) (err error) { } }) - const ( - errMsg = "setting control options" - errMsgFmt = errMsg + ": %w" - ) + err = errors.Join(err, cerr) - if err != nil && cerr != nil { - err = errors.List(errMsg, err, cerr) - } else if err != nil { - err = fmt.Errorf(errMsgFmt, err) - } else if cerr != nil { - err = fmt.Errorf(errMsgFmt, cerr) - } - - return err + return errors.Annotate(err, "setting control options: %w") } // listenPacketReusable announces on the local network address additionally diff --git a/internal/aghnet/ipset_linux.go b/internal/aghnet/ipset_linux.go index 1c970f53..bf4c8c86 100644 --- a/internal/aghnet/ipset_linux.go +++ b/internal/aghnet/ipset_linux.go @@ -390,9 +390,5 @@ func (m *ipsetMgr) Close() (err error) { errs = append(errs, err) } - if len(errs) != 0 { - return errors.List("closing ipsets", errs...) - } - - return nil + return errors.Annotate(errors.Join(errs...), "closing ipsets: %w") } diff --git a/internal/aghnet/net_internal_test.go b/internal/aghnet/net_internal_test.go index 9c4cff8c..8c5d9c8e 100644 --- a/internal/aghnet/net_internal_test.go +++ b/internal/aghnet/net_internal_test.go @@ -7,7 +7,6 @@ import ( "io/fs" "net" "net/netip" - "os" "strings" "testing" @@ -18,9 +17,6 @@ import ( "github.com/stretchr/testify/require" ) -// testdata is the filesystem containing data for testing the package. -var testdata fs.FS = os.DirFS("./testdata") - // substRootDirFS replaces the aghos.RootDirFS function used throughout the // package with fsys for tests ran under t. func substRootDirFS(t testing.TB, fsys fs.FS) { diff --git a/internal/aghos/fswatcher.go b/internal/aghos/fswatcher.go index 8f5d1d60..32d88f21 100644 --- a/internal/aghos/fswatcher.go +++ b/internal/aghos/fswatcher.go @@ -113,7 +113,7 @@ func (w *osWatcher) handleEvents() { } // Skip the following events assuming that sometimes the same event - // occurrs several times. + // occurs several times. for ok := true; ok; { select { case _, ok = <-ch: diff --git a/internal/aghos/os.go b/internal/aghos/os.go index 40c79964..96e5661a 100644 --- a/internal/aghos/os.go +++ b/internal/aghos/os.go @@ -182,3 +182,8 @@ func IsReconfigureSignal(sig os.Signal) (ok bool) { func IsShutdownSignal(sig os.Signal) (ok bool) { return isShutdownSignal(sig) } + +// SendShutdownSignal sends the shutdown signal to the channel. +func SendShutdownSignal(c chan<- os.Signal) { + sendShutdownSignal(c) +} diff --git a/internal/aghos/os_unix.go b/internal/aghos/os_unix.go index 6dcc5717..b6ba0a21 100644 --- a/internal/aghos/os_unix.go +++ b/internal/aghos/os_unix.go @@ -37,3 +37,7 @@ func isShutdownSignal(sig os.Signal) (ok bool) { return false } } + +func sendShutdownSignal(_ chan<- os.Signal) { + // On Unix we are already notified by the system. +} diff --git a/internal/aghos/os_windows.go b/internal/aghos/os_windows.go index 5568ef4c..b30aa719 100644 --- a/internal/aghos/os_windows.go +++ b/internal/aghos/os_windows.go @@ -77,3 +77,7 @@ func isShutdownSignal(sig os.Signal) (ok bool) { return false } } + +func sendShutdownSignal(c chan<- os.Signal) { + c <- os.Interrupt +} diff --git a/internal/aghtest/aghtest.go b/internal/aghtest/aghtest.go index dfe0551d..3297795a 100644 --- a/internal/aghtest/aghtest.go +++ b/internal/aghtest/aghtest.go @@ -4,12 +4,20 @@ package aghtest import ( "crypto/sha256" "io" - "net" + "net/netip" "testing" "github.com/AdguardTeam/golibs/log" ) +const ( + // ReqHost is the common request host for filtering tests. + ReqHost = "www.host.example" + + // ReqFQDN is the common request FQDN for filtering tests. + ReqFQDN = ReqHost + "." +) + // ReplaceLogWriter moves logger output to w and uses Cleanup method of t to // revert changes. func ReplaceLogWriter(t testing.TB, w io.Writer) { @@ -38,8 +46,8 @@ func ReplaceLogLevel(t testing.TB, l log.Level) { } // HostToIPs is a helper that generates one IPv4 and one IPv6 address from host. -func HostToIPs(host string) (ipv4, ipv6 net.IP) { +func HostToIPs(host string) (ipv4, ipv6 netip.Addr) { hash := sha256.Sum256([]byte(host)) - return net.IP(hash[:4]), net.IP(hash[4:20]) + return netip.AddrFrom4([4]byte(hash[:4])), netip.AddrFrom16([16]byte(hash[4:20])) } diff --git a/internal/aghnet/arpdb.go b/internal/arpdb/arpdb.go similarity index 61% rename from internal/aghnet/arpdb.go rename to internal/arpdb/arpdb.go index a63e5cd2..66b1ae1e 100644 --- a/internal/aghnet/arpdb.go +++ b/internal/arpdb/arpdb.go @@ -1,4 +1,5 @@ -package aghnet +// Package arpdb implements the Network Neighborhood Database. +package arpdb import ( "bufio" @@ -8,15 +9,25 @@ import ( "net/netip" "sync" + "github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/netutil" "golang.org/x/exp/slices" ) -// ARPDB: The Network Neighborhood Database +// Variables and functions to substitute in tests. +var ( + // aghosRunCommand is the function to run shell commands. + aghosRunCommand = aghos.RunCommand -// ARPDB stores and refreshes the network neighborhood reported by ARP (Address -// Resolution Protocol). -type ARPDB interface { + // rootDirFS is the filesystem pointing to the root directory. + rootDirFS = aghos.RootDirFS() +) + +// Interface stores and refreshes the network neighborhood reported by ARP +// (Address Resolution Protocol). +type Interface interface { // Refresh updates the stored data. It must be safe for concurrent use. Refresh() (err error) @@ -25,28 +36,24 @@ type ARPDB interface { Neighbors() (ns []Neighbor) } -// NewARPDB returns the ARPDB properly initialized for the OS. -func NewARPDB() (arp ARPDB) { +// New returns the [Interface] properly initialized for the OS. +func New() (arp Interface) { return newARPDB() } -// Empty ARPDB implementation - -// EmptyARPDB is the ARPDB implementation that does nothing. -type EmptyARPDB struct{} +// Empty is the [Interface] implementation that does nothing. +type Empty struct{} // type check -var _ ARPDB = EmptyARPDB{} +var _ Interface = Empty{} -// Refresh implements the ARPDB interface for EmptyARPContainer. It does +// Refresh implements the [Interface] interface for EmptyARPContainer. It does // nothing and always returns nil error. -func (EmptyARPDB) Refresh() (err error) { return nil } +func (Empty) Refresh() (err error) { return nil } -// Neighbors implements the ARPDB interface for EmptyARPContainer. It always -// returns nil. -func (EmptyARPDB) Neighbors() (ns []Neighbor) { return nil } - -// ARPDB Helper Types +// Neighbors implements the [Interface] interface for EmptyARPContainer. It +// always returns nil. +func (Empty) Neighbors() (ns []Neighbor) { return nil } // Neighbor is the pair of IP address and MAC address reported by ARP. type Neighbor struct { @@ -70,8 +77,21 @@ func (n Neighbor) Clone() (clone Neighbor) { } } +// validatedHostname returns h if it's a valid hostname, or an empty string +// otherwise, logging the validation error. +func validatedHostname(h string) (host string) { + err := netutil.ValidateHostname(h) + if err != nil { + log.Debug("arpdb: parsing arp output: host: %s", err) + + return "" + } + + return h +} + // neighs is the helper type that stores neighbors to avoid copying its methods -// among all the ARPDB implementations. +// among all the [Interface] implementations. type neighs struct { mu *sync.RWMutex ns []Neighbor @@ -108,14 +128,12 @@ func (ns *neighs) reset(with []Neighbor) { ns.ns = with } -// Command ARPDB - // parseNeighsFunc parses the text from sc as if it'd be an output of some // ARP-related command. lenHint is a hint for the size of the allocated slice // of Neighbors. type parseNeighsFunc func(sc *bufio.Scanner, lenHint int) (ns []Neighbor) -// cmdARPDB is the implementation of the ARPDB that uses command line to +// cmdARPDB is the implementation of the [Interface] that uses command line to // retrieve data. type cmdARPDB struct { parse parseNeighsFunc @@ -125,9 +143,9 @@ type cmdARPDB struct { } // type check -var _ ARPDB = (*cmdARPDB)(nil) +var _ Interface = (*cmdARPDB)(nil) -// Refresh implements the ARPDB interface for *cmdARPDB. +// Refresh implements the [Interface] interface for *cmdARPDB. func (arp *cmdARPDB) Refresh() (err error) { defer func() { err = errors.Annotate(err, "cmd arpdb: %w") }() @@ -150,24 +168,22 @@ func (arp *cmdARPDB) Refresh() (err error) { return nil } -// Neighbors implements the ARPDB interface for *cmdARPDB. +// Neighbors implements the [Interface] interface for *cmdARPDB. func (arp *cmdARPDB) Neighbors() (ns []Neighbor) { return arp.ns.clone() } -// Composite ARPDB - -// arpdbs is the ARPDB that combines several ARPDB implementations and -// consequently switches between those. +// arpdbs is the [Interface] that combines several [Interface] implementations +// and consequently switches between those. type arpdbs struct { - // arps is the set of ARPDB implementations to range through. - arps []ARPDB + // arps is the set of [Interface] implementations to range through. + arps []Interface neighs } // newARPDBs returns a properly initialized *arpdbs. It begins refreshing from // the first of arps. -func newARPDBs(arps ...ARPDB) (arp *arpdbs) { +func newARPDBs(arps ...Interface) (arp *arpdbs) { return &arpdbs{ arps: arps, neighs: neighs{ @@ -178,9 +194,9 @@ func newARPDBs(arps ...ARPDB) (arp *arpdbs) { } // type check -var _ ARPDB = (*arpdbs)(nil) +var _ Interface = (*arpdbs)(nil) -// Refresh implements the ARPDB interface for *arpdbs. +// Refresh implements the [Interface] interface for *arpdbs. func (arp *arpdbs) Refresh() (err error) { var errs []error @@ -197,14 +213,10 @@ func (arp *arpdbs) Refresh() (err error) { return nil } - if len(errs) > 0 { - err = errors.List("each arpdb failed", errs...) - } - - return err + return errors.Annotate(errors.Join(errs...), "each arpdb failed: %w") } -// Neighbors implements the ARPDB interface for *arpdbs. +// Neighbors implements the [Interface] interface for *arpdbs. // // TODO(e.burkov): Think of a way to avoid cloning the slice twice. func (arp *arpdbs) Neighbors() (ns []Neighbor) { diff --git a/internal/aghnet/arpdb_bsd.go b/internal/arpdb/arpdb_bsd.go similarity index 74% rename from internal/aghnet/arpdb_bsd.go rename to internal/arpdb/arpdb_bsd.go index 50287785..c9658dbb 100644 --- a/internal/aghnet/arpdb_bsd.go +++ b/internal/arpdb/arpdb_bsd.go @@ -1,6 +1,6 @@ //go:build darwin || freebsd -package aghnet +package arpdb import ( "bufio" @@ -10,7 +10,6 @@ import ( "sync" "github.com/AdguardTeam/golibs/log" - "github.com/AdguardTeam/golibs/netutil" ) func newARPDB() (arp *cmdARPDB) { @@ -44,16 +43,16 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { continue } - n := Neighbor{} - - if ipStr := fields[1]; len(ipStr) < 2 { + ipStr := fields[1] + if len(ipStr) < 2 { continue - } else if ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1]); err != nil { + } + + ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1]) + if err != nil { log.Debug("arpdb: parsing arp output: ip: %s", err) continue - } else { - n.IP = ip } hwStr := fields[3] @@ -62,19 +61,13 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { log.Debug("arpdb: parsing arp output: mac: %s", err) continue - } else { - n.MAC = mac } - host := fields[0] - err = netutil.ValidateHostname(host) - if err != nil { - log.Debug("arpdb: parsing arp output: host: %s", err) - } else { - n.Name = host - } - - ns = append(ns, n) + ns = append(ns, Neighbor{ + IP: ip, + MAC: mac, + Name: validatedHostname(fields[0]), + }) } return ns diff --git a/internal/aghnet/arpdb_bsd_test.go b/internal/arpdb/arpdb_bsd_internal_test.go similarity index 98% rename from internal/aghnet/arpdb_bsd_test.go rename to internal/arpdb/arpdb_bsd_internal_test.go index b6bd6b9d..c8fc8e79 100644 --- a/internal/aghnet/arpdb_bsd_test.go +++ b/internal/arpdb/arpdb_bsd_internal_test.go @@ -1,6 +1,6 @@ //go:build darwin || freebsd -package aghnet +package arpdb import ( "net" diff --git a/internal/aghnet/arpdb_test.go b/internal/arpdb/arpdb_internal_test.go similarity index 69% rename from internal/aghnet/arpdb_test.go rename to internal/arpdb/arpdb_internal_test.go index ab40ab6f..dfd57614 100644 --- a/internal/aghnet/arpdb_test.go +++ b/internal/arpdb/arpdb_internal_test.go @@ -1,8 +1,12 @@ -package aghnet +package arpdb import ( + "fmt" + "io/fs" "net" "net/netip" + "os" + "strings" "sync" "testing" @@ -12,30 +16,78 @@ import ( "github.com/stretchr/testify/require" ) -func TestNewARPDB(t *testing.T) { - var a ARPDB - require.NotPanics(t, func() { a = NewARPDB() }) +// testdata is the filesystem containing data for testing the package. +var testdata fs.FS = os.DirFS("./testdata") + +// RunCmdFunc is the signature of aghos.RunCommand function. +type RunCmdFunc func(cmd string, args ...string) (code int, out []byte, err error) + +// substShell replaces the the aghos.RunCommand function used throughout the +// package with rc for tests ran under t. +func substShell(t testing.TB, rc RunCmdFunc) { + t.Helper() + + prev := aghosRunCommand + t.Cleanup(func() { aghosRunCommand = prev }) + aghosRunCommand = rc +} + +// mapShell is a substitution of aghos.RunCommand that maps the command to it's +// execution result. It's only needed to simplify testing. +// +// TODO(e.burkov): Perhaps put all the shell interactions behind an interface. +type mapShell map[string]struct { + err error + out string + code int +} + +// theOnlyCmd returns mapShell that only handles a single command and arguments +// combination from cmd. +func theOnlyCmd(cmd string, code int, out string, err error) (s mapShell) { + return mapShell{cmd: {code: code, out: out, err: err}} +} + +// RunCmd is a RunCmdFunc handled by s. +func (s mapShell) RunCmd(cmd string, args ...string) (code int, out []byte, err error) { + key := strings.Join(append([]string{cmd}, args...), " ") + ret, ok := s[key] + if !ok { + return 0, nil, fmt.Errorf("unexpected shell command %q", key) + } + + return ret.code, []byte(ret.out), ret.err +} + +func Test_New(t *testing.T) { + var a Interface + require.NotPanics(t, func() { a = New() }) assert.NotNil(t, a) } -// TestARPDB is the mock implementation of ARPDB to use in tests. +// TODO(s.chzhen): Consider moving mocks into aghtest. + +// TestARPDB is the mock implementation of [Interface] to use in tests. type TestARPDB struct { OnRefresh func() (err error) OnNeighbors func() (ns []Neighbor) } -// Refresh implements the ARPDB interface for *TestARPDB. +// type check +var _ Interface = (*TestARPDB)(nil) + +// Refresh implements the [Interface] interface for *TestARPDB. func (arp *TestARPDB) Refresh() (err error) { return arp.OnRefresh() } -// Neighbors implements the ARPDB interface for *TestARPDB. +// Neighbors implements the [Interface] interface for *TestARPDB. func (arp *TestARPDB) Neighbors() (ns []Neighbor) { return arp.OnNeighbors() } -func TestARPDBS(t *testing.T) { +func Test_NewARPDBs(t *testing.T) { knownIP := netip.MustParseAddr("1.2.3.4") knownMAC := net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF} @@ -82,7 +134,7 @@ func TestARPDBS(t *testing.T) { t.Run("fail_only", func(t *testing.T) { t.Cleanup(clnp) - wantMsg := `each arpdb failed: 2 errors: "refresh failed", "refresh failed"` + wantMsg := "each arpdb failed: refresh failed\nrefresh failed" a := newARPDBs(failDB, failDB) err := a.Refresh() @@ -195,7 +247,7 @@ func TestCmdARPDB_arpa(t *testing.T) { } func TestEmptyARPDB(t *testing.T) { - a := EmptyARPDB{} + a := Empty{} t.Run("refresh", func(t *testing.T) { var err error diff --git a/internal/aghnet/arpdb_linux.go b/internal/arpdb/arpdb_linux.go similarity index 78% rename from internal/aghnet/arpdb_linux.go rename to internal/arpdb/arpdb_linux.go index d3ebe4a7..0cf7f0ef 100644 --- a/internal/aghnet/arpdb_linux.go +++ b/internal/arpdb/arpdb_linux.go @@ -1,6 +1,6 @@ //go:build linux -package aghnet +package arpdb import ( "bufio" @@ -13,7 +13,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/golibs/log" - "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" ) @@ -68,9 +67,9 @@ type fsysARPDB struct { } // type check -var _ ARPDB = (*fsysARPDB)(nil) +var _ Interface = (*fsysARPDB)(nil) -// Refresh implements the ARPDB interface for *fsysARPDB. +// Refresh implements the [Interface] interface for *fsysARPDB. func (arp *fsysARPDB) Refresh() (err error) { var f fs.File f, err = arp.fsys.Open(arp.filename) @@ -88,21 +87,10 @@ func (arp *fsysARPDB) Refresh() (err error) { ns := make([]Neighbor, 0, arp.ns.len()) for sc.Scan() { - ln := sc.Text() - fields := stringutil.SplitTrimmed(ln, " ") - if len(fields) != 6 { - continue + n := parseNeighbor(sc.Text()) + if n != nil { + ns = append(ns, *n) } - - n := Neighbor{} - n.IP, err = netip.ParseAddr(fields[0]) - if err != nil || n.IP.IsUnspecified() { - continue - } else if n.MAC, err = net.ParseMAC(fields[3]); err != nil { - continue - } - - ns = append(ns, n) } arp.ns.reset(ns) @@ -110,7 +98,30 @@ func (arp *fsysARPDB) Refresh() (err error) { return nil } -// Neighbors implements the ARPDB interface for *fsysARPDB. +// parseNeighbor parses line into *Neighbor. +func parseNeighbor(line string) (n *Neighbor) { + fields := stringutil.SplitTrimmed(line, " ") + if len(fields) != 6 { + return nil + } + + ip, err := netip.ParseAddr(fields[0]) + if err != nil || ip.IsUnspecified() { + return nil + } + + mac, err := net.ParseMAC(fields[3]) + if err != nil { + return nil + } + + return &Neighbor{ + IP: ip, + MAC: mac, + } +} + +// Neighbors implements the [Interface] interface for *fsysARPDB. func (arp *fsysARPDB) Neighbors() (ns []Neighbor) { return arp.ns.clone() } @@ -135,15 +146,11 @@ func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { continue } - n := Neighbor{} - ip, err := netip.ParseAddr(fields[0]) - if err != nil || n.IP.IsUnspecified() { + if err != nil { log.Debug("arpdb: parsing arp output: ip: %s", err) continue - } else { - n.IP = ip } hwStr := fields[3] @@ -152,11 +159,12 @@ func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { log.Debug("arpdb: parsing arp output: mac: %s", err) continue - } else { - n.MAC = mac } - ns = append(ns, n) + ns = append(ns, Neighbor{ + IP: ip, + MAC: mac, + }) } return ns @@ -176,35 +184,31 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { continue } - n := Neighbor{} - - if ipStr := fields[1]; len(ipStr) < 2 { + ipStr := fields[1] + if len(ipStr) < 2 { continue - } else if ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1]); err != nil { + } + + ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1]) + if err != nil { log.Debug("arpdb: parsing arp output: ip: %s", err) continue - } else { - n.IP = ip } hwStr := fields[3] - if mac, err := net.ParseMAC(hwStr); err != nil { + mac, err := net.ParseMAC(hwStr) + if err != nil { log.Debug("arpdb: parsing arp output: mac: %s", err) continue - } else { - n.MAC = mac } - host := fields[0] - if verr := netutil.ValidateHostname(host); verr != nil { - log.Debug("arpdb: parsing arp output: host: %s", verr) - } else { - n.Name = host - } - - ns = append(ns, n) + ns = append(ns, Neighbor{ + IP: ip, + MAC: mac, + Name: validatedHostname(fields[0]), + }) } return ns diff --git a/internal/aghnet/arpdb_linux_test.go b/internal/arpdb/arpdb_linux_internal_test.go similarity index 99% rename from internal/aghnet/arpdb_linux_test.go rename to internal/arpdb/arpdb_linux_internal_test.go index d07c654d..44c76843 100644 --- a/internal/aghnet/arpdb_linux_test.go +++ b/internal/arpdb/arpdb_linux_internal_test.go @@ -1,6 +1,6 @@ //go:build linux -package aghnet +package arpdb import ( "net" diff --git a/internal/aghnet/arpdb_openbsd.go b/internal/arpdb/arpdb_openbsd.go similarity index 98% rename from internal/aghnet/arpdb_openbsd.go rename to internal/arpdb/arpdb_openbsd.go index 2b356d06..7b60737b 100644 --- a/internal/aghnet/arpdb_openbsd.go +++ b/internal/arpdb/arpdb_openbsd.go @@ -1,6 +1,6 @@ //go:build openbsd -package aghnet +package arpdb import ( "bufio" diff --git a/internal/aghnet/arpdb_openbsd_test.go b/internal/arpdb/arpdb_openbsd_internal_test.go similarity index 97% rename from internal/aghnet/arpdb_openbsd_test.go rename to internal/arpdb/arpdb_openbsd_internal_test.go index a324ed9c..ae7d7a46 100644 --- a/internal/aghnet/arpdb_openbsd_test.go +++ b/internal/arpdb/arpdb_openbsd_internal_test.go @@ -1,6 +1,6 @@ //go:build openbsd -package aghnet +package arpdb import ( "net" diff --git a/internal/aghnet/arpdb_windows.go b/internal/arpdb/arpdb_windows.go similarity index 81% rename from internal/aghnet/arpdb_windows.go rename to internal/arpdb/arpdb_windows.go index ed4c8682..3b4bb725 100644 --- a/internal/aghnet/arpdb_windows.go +++ b/internal/arpdb/arpdb_windows.go @@ -1,6 +1,6 @@ //go:build windows -package aghnet +package arpdb import ( "bufio" @@ -8,6 +8,8 @@ import ( "net/netip" "strings" "sync" + + "github.com/AdguardTeam/golibs/log" ) func newARPDB() (arp *cmdARPDB) { @@ -42,23 +44,24 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { continue } - n := Neighbor{} - ip, err := netip.ParseAddr(fields[0]) if err != nil { + log.Debug("arpdb: parsing arp output: ip: %s", err) + continue - } else { - n.IP = ip } mac, err := net.ParseMAC(fields[1]) if err != nil { + log.Debug("arpdb: parsing arp output: mac: %s", err) + continue - } else { - n.MAC = mac } - ns = append(ns, n) + ns = append(ns, Neighbor{ + IP: ip, + MAC: mac, + }) } return ns diff --git a/internal/aghnet/arpdb_windows_test.go b/internal/arpdb/arpdb_windows_internal_test.go similarity index 97% rename from internal/aghnet/arpdb_windows_test.go rename to internal/arpdb/arpdb_windows_internal_test.go index c3dcfe04..a909048c 100644 --- a/internal/aghnet/arpdb_windows_test.go +++ b/internal/arpdb/arpdb_windows_internal_test.go @@ -1,6 +1,6 @@ //go:build windows -package aghnet +package arpdb import ( "net" diff --git a/internal/aghnet/testdata/proc_net_arp b/internal/arpdb/testdata/proc_net_arp similarity index 100% rename from internal/aghnet/testdata/proc_net_arp rename to internal/arpdb/testdata/proc_net_arp diff --git a/internal/client/client.go b/internal/client/client.go index 6cfcec79..6b81b9b8 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -3,3 +3,52 @@ // // TODO(a.garipov): Expand. package client + +import ( + "encoding" + "fmt" +) + +// Source represents the source from which the information about the client has +// been obtained. +type Source uint8 + +// Clients information sources. The order determines the priority. +const ( + SourceNone Source = iota + SourceWHOIS + SourceARP + SourceRDNS + SourceDHCP + SourceHostsFile + SourcePersistent +) + +// type check +var _ fmt.Stringer = Source(0) + +// String returns a human-readable name of cs. +func (cs Source) String() (s string) { + switch cs { + case SourceWHOIS: + return "WHOIS" + case SourceARP: + return "ARP" + case SourceRDNS: + return "rDNS" + case SourceDHCP: + return "DHCP" + case SourceHostsFile: + return "etc/hosts" + default: + return "" + } +} + +// type check +var _ encoding.TextMarshaler = Source(0) + +// MarshalText implements encoding.TextMarshaler for the Source. +func (cs Source) MarshalText() (text []byte, err error) { + return []byte(cs.String()), nil +} diff --git a/internal/home/upgrade_test.go b/internal/confmigrate/migrations_test.go similarity index 74% rename from internal/home/upgrade_test.go rename to internal/confmigrate/migrations_test.go index a440ccfc..513646e8 100644 --- a/internal/home/upgrade_test.go +++ b/internal/confmigrate/migrations_test.go @@ -1,4 +1,4 @@ -package home +package confmigrate import ( "testing" @@ -11,12 +11,16 @@ import ( "github.com/stretchr/testify/require" ) -// TODO(a.garipov): Cover all migrations, use a testdata/ dir. +// TODO(e.burkov): Cover all migrations, use a testdata/ dir. func TestUpgradeSchema1to2(t *testing.T) { diskConf := testDiskConf(1) - err := upgradeSchema1to2(diskConf) + m := New(&Config{ + WorkingDir: "", + }) + + err := m.migrateTo2(diskConf) require.NoError(t, err) require.Equal(t, diskConf["schema_version"], 2) @@ -39,7 +43,7 @@ func TestUpgradeSchema1to2(t *testing.T) { func TestUpgradeSchema2to3(t *testing.T) { diskConf := testDiskConf(2) - err := upgradeSchema2to3(diskConf) + err := migrateTo3(diskConf) require.NoError(t, err) require.Equal(t, diskConf["schema_version"], 3) @@ -52,7 +56,7 @@ func TestUpgradeSchema2to3(t *testing.T) { bootstrapDNS := newDNSConf["bootstrap_dns"] switch v := bootstrapDNS.(type) { - case []string: + case yarr: require.Len(t, v, 1) require.Equal(t, "8.8.8.8:53", v[0]) default: @@ -78,21 +82,21 @@ func TestUpgradeSchema5to6(t *testing.T) { name string }{{ in: yobj{ - "clients": []yobj{}, + "clients": yarr{}, }, want: yobj{ - "clients": []yobj{}, + "clients": yarr{}, "schema_version": newSchemaVer, }, wantErr: "", name: "no_clients", }, { in: yobj{ - "clients": []yobj{{"ip": "127.0.0.1"}}, + "clients": yarr{yobj{"ip": "127.0.0.1"}}, }, want: yobj{ - "clients": []yobj{{ - "ids": []string{"127.0.0.1"}, + "clients": yarr{yobj{ + "ids": yarr{"127.0.0.1"}, "ip": "127.0.0.1", }}, "schema_version": newSchemaVer, @@ -101,11 +105,11 @@ func TestUpgradeSchema5to6(t *testing.T) { name: "client_ip", }, { in: yobj{ - "clients": []yobj{{"mac": "mac"}}, + "clients": yarr{yobj{"mac": "mac"}}, }, want: yobj{ - "clients": []yobj{{ - "ids": []string{"mac"}, + "clients": yarr{yobj{ + "ids": yarr{"mac"}, "mac": "mac", }}, "schema_version": newSchemaVer, @@ -114,11 +118,11 @@ func TestUpgradeSchema5to6(t *testing.T) { name: "client_mac", }, { in: yobj{ - "clients": []yobj{{"ip": "127.0.0.1", "mac": "mac"}}, + "clients": yarr{yobj{"ip": "127.0.0.1", "mac": "mac"}}, }, want: yobj{ - "clients": []yobj{{ - "ids": []string{"127.0.0.1", "mac"}, + "clients": yarr{yobj{ + "ids": yarr{"127.0.0.1", "mac"}, "ip": "127.0.0.1", "mac": "mac", }}, @@ -128,29 +132,29 @@ func TestUpgradeSchema5to6(t *testing.T) { name: "client_ip_mac", }, { in: yobj{ - "clients": []yobj{{"ip": 1, "mac": "mac"}}, + "clients": yarr{yobj{"ip": 1, "mac": "mac"}}, }, want: yobj{ - "clients": []yobj{{"ip": 1, "mac": "mac"}}, + "clients": yarr{yobj{"ip": 1, "mac": "mac"}}, "schema_version": newSchemaVer, }, - wantErr: "client.ip is not a string: 1", + wantErr: `client at index 0: unexpected type of "ip": int`, name: "inv_client_ip", }, { in: yobj{ - "clients": []yobj{{"ip": "127.0.0.1", "mac": 1}}, + "clients": yarr{yobj{"ip": "127.0.0.1", "mac": 1}}, }, want: yobj{ - "clients": []yobj{{"ip": "127.0.0.1", "mac": 1}}, + "clients": yarr{yobj{"ip": "127.0.0.1", "mac": 1}}, "schema_version": newSchemaVer, }, - wantErr: "client.mac is not a string: 1", + wantErr: `client at index 0: unexpected type of "mac": int`, name: "inv_client_mac", }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema5to6(tc.in) + err := migrateTo6(tc.in) testutil.AssertErrorMsg(t, tc.wantErr, err) assert.Equal(t, tc.want, tc.in) }) @@ -166,7 +170,7 @@ func TestUpgradeSchema7to8(t *testing.T) { "schema_version": 7, } - err := upgradeSchema7to8(oldConf) + err := migrateTo8(oldConf) require.NoError(t, err) require.Equal(t, oldConf["schema_version"], 8) @@ -194,7 +198,7 @@ func TestUpgradeSchema8to9(t *testing.T) { "schema_version": 8, } - err := upgradeSchema8to9(oldConf) + err := migrateTo9(oldConf) require.NoError(t, err) require.Equal(t, oldConf["schema_version"], 9) @@ -217,7 +221,7 @@ func TestUpgradeSchema8to9(t *testing.T) { "schema_version": 8, } - err := upgradeSchema8to9(oldConf) + err := migrateTo9(oldConf) require.NoError(t, err) require.Equal(t, oldConf["schema_version"], 9) @@ -404,12 +408,12 @@ func TestUpgradeSchema9to10(t *testing.T) { }, { ups: ultimateAns, want: nil, - wantErr: "unexpected type of dns.upstream_dns: int", + wantErr: `unexpected type of "upstream_dns": int`, name: "bad_yarr_type", }, { ups: yarr{ultimateAns}, want: nil, - wantErr: "unexpected type of upstream field: int", + wantErr: `unexpected type of upstream field: int`, name: "bad_upstream_type", }} @@ -421,7 +425,7 @@ func TestUpgradeSchema9to10(t *testing.T) { "schema_version": 9, } t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema9to10(conf) + err := migrateTo10(conf) if tc.wantErr != "" { testutil.AssertErrorMsg(t, tc.wantErr, err) @@ -446,17 +450,17 @@ func TestUpgradeSchema9to10(t *testing.T) { } t.Run("no_dns", func(t *testing.T) { - err := upgradeSchema9to10(yobj{}) + err := migrateTo10(yobj{}) assert.NoError(t, err) }) t.Run("bad_dns", func(t *testing.T) { - err := upgradeSchema9to10(yobj{ + err := migrateTo10(yobj{ "dns": ultimateAns, }) - testutil.AssertErrorMsg(t, "unexpected type of dns: int", err) + testutil.AssertErrorMsg(t, `unexpected type of "dns": int`, err) }) } @@ -464,7 +468,7 @@ func TestUpgradeSchema10to11(t *testing.T) { check := func(t *testing.T, conf yobj) { rlimit, _ := conf["rlimit_nofile"].(int) - err := upgradeSchema10to11(conf) + err := migrateTo11(conf) require.NoError(t, err) require.Equal(t, conf["schema_version"], 11) @@ -521,7 +525,7 @@ func TestUpgradeSchema11to12(t *testing.T) { }, { ivl: 0.25, want: 0, - wantErr: "unexpected type of querylog_interval: float64", + wantErr: `unexpected type of "querylog_interval": float64`, name: "fail", }} @@ -533,7 +537,7 @@ func TestUpgradeSchema11to12(t *testing.T) { "schema_version": 11, } t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema11to12(conf) + err := migrateTo12(conf) if tc.wantErr != "" { require.Error(t, err) @@ -562,17 +566,17 @@ func TestUpgradeSchema11to12(t *testing.T) { } t.Run("no_dns", func(t *testing.T) { - err := upgradeSchema11to12(yobj{}) + err := migrateTo12(yobj{}) assert.NoError(t, err) }) t.Run("bad_dns", func(t *testing.T) { - err := upgradeSchema11to12(yobj{ + err := migrateTo12(yobj{ "dns": 0, }) - testutil.AssertErrorMsg(t, "unexpected type of dns: int", err) + testutil.AssertErrorMsg(t, `unexpected type of "dns": int`, err) }) t.Run("no_field", func(t *testing.T) { @@ -580,7 +584,7 @@ func TestUpgradeSchema11to12(t *testing.T) { "dns": yobj{}, } - err := upgradeSchema11to12(conf) + err := migrateTo12(conf) require.NoError(t, err) dns, ok := conf["dns"] @@ -640,7 +644,7 @@ func TestUpgradeSchema12to13(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema12to13(tc.in) + err := migrateTo13(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -651,10 +655,10 @@ func TestUpgradeSchema12to13(t *testing.T) { func TestUpgradeSchema13to14(t *testing.T) { const newSchemaVer = 14 - testClient := &clientObject{ - Name: "agh-client", - IDs: []string{"id1"}, - UseGlobalSettings: true, + testClient := yobj{ + "name": "agh-client", + "ids": []string{"id1"}, + "use_global_settings": true, } testCases := []struct { @@ -668,37 +672,37 @@ func TestUpgradeSchema13to14(t *testing.T) { // The clients field will be added anyway. "clients": yobj{ "persistent": yarr{}, - "runtime_sources": &clientSourcesConfig{ - WHOIS: true, - ARP: true, - RDNS: false, - DHCP: true, - HostsFile: true, + "runtime_sources": yobj{ + "whois": true, + "arp": true, + "rdns": false, + "dhcp": true, + "hosts": true, }, }, }, name: "no_clients", }, { in: yobj{ - "clients": []*clientObject{testClient}, + "clients": yarr{testClient}, }, want: yobj{ "schema_version": newSchemaVer, "clients": yobj{ - "persistent": []*clientObject{testClient}, - "runtime_sources": &clientSourcesConfig{ - WHOIS: true, - ARP: true, - RDNS: false, - DHCP: true, - HostsFile: true, + "persistent": yarr{testClient}, + "runtime_sources": yobj{ + "whois": true, + "arp": true, + "rdns": false, + "dhcp": true, + "hosts": true, }, }, }, name: "no_dns", }, { in: yobj{ - "clients": []*clientObject{testClient}, + "clients": yarr{testClient}, "dns": yobj{ "resolve_clients": true, }, @@ -706,13 +710,13 @@ func TestUpgradeSchema13to14(t *testing.T) { want: yobj{ "schema_version": newSchemaVer, "clients": yobj{ - "persistent": []*clientObject{testClient}, - "runtime_sources": &clientSourcesConfig{ - WHOIS: true, - ARP: true, - RDNS: true, - DHCP: true, - HostsFile: true, + "persistent": yarr{testClient}, + "runtime_sources": yobj{ + "whois": true, + "arp": true, + "rdns": true, + "dhcp": true, + "hosts": true, }, }, "dns": yobj{}, @@ -722,7 +726,7 @@ func TestUpgradeSchema13to14(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema13to14(tc.in) + err := migrateTo14(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -770,7 +774,7 @@ func TestUpgradeSchema14to15(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema14to15(tc.in) + err := migrateTo15(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -829,7 +833,7 @@ func TestUpgradeSchema15to16(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema15to16(tc.in) + err := migrateTo16(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -890,7 +894,7 @@ func TestUpgradeSchema16to17(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema16to17(tc.in) + err := migrateTo17(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -949,7 +953,7 @@ func TestUpgradeSchema17to18(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema17to18(tc.in) + err := migrateTo18(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -962,7 +966,7 @@ func TestUpgradeSchema18to19(t *testing.T) { defaultWantObj := yobj{ "clients": yobj{ - "persistent": []yobj{{ + "persistent": yarr{yobj{ "name": "localhost", "safe_search": yobj{ "enabled": true, @@ -994,7 +998,7 @@ func TestUpgradeSchema18to19(t *testing.T) { }, { in: yobj{ "clients": yobj{ - "persistent": []yobj{{"name": "localhost"}}, + "persistent": yarr{yobj{"name": "localhost"}}, }, }, want: defaultWantObj, @@ -1002,7 +1006,7 @@ func TestUpgradeSchema18to19(t *testing.T) { }, { in: yobj{ "clients": yobj{ - "persistent": []yobj{{"name": "localhost", "safesearch_enabled": true}}, + "persistent": yarr{yobj{"name": "localhost", "safesearch_enabled": true}}, }, }, want: defaultWantObj, @@ -1010,11 +1014,11 @@ func TestUpgradeSchema18to19(t *testing.T) { }, { in: yobj{ "clients": yobj{ - "persistent": []yobj{{"name": "localhost", "safesearch_enabled": false}}, + "persistent": yarr{yobj{"name": "localhost", "safesearch_enabled": false}}, }, }, want: yobj{ - "clients": yobj{"persistent": []yobj{{ + "clients": yobj{"persistent": yarr{yobj{ "name": "localhost", "safe_search": yobj{ "enabled": false, @@ -1033,7 +1037,7 @@ func TestUpgradeSchema18to19(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema18to19(tc.in) + err := migrateTo19(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -1060,7 +1064,7 @@ func TestUpgradeSchema19to20(t *testing.T) { }, { ivl: 0.25, want: 0, - wantErr: "unexpected type of interval: float64", + wantErr: `unexpected type of "interval": float64`, name: "fail", }} @@ -1072,7 +1076,7 @@ func TestUpgradeSchema19to20(t *testing.T) { "schema_version": 19, } t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema19to20(conf) + err := migrateTo20(conf) if tc.wantErr != "" { require.Error(t, err) @@ -1101,17 +1105,17 @@ func TestUpgradeSchema19to20(t *testing.T) { } t.Run("no_stats", func(t *testing.T) { - err := upgradeSchema19to20(yobj{}) + err := migrateTo20(yobj{}) assert.NoError(t, err) }) t.Run("bad_stats", func(t *testing.T) { - err := upgradeSchema19to20(yobj{ + err := migrateTo20(yobj{ "statistics": 0, }) - testutil.AssertErrorMsg(t, "unexpected type of stats: int", err) + testutil.AssertErrorMsg(t, `unexpected type of "statistics": int`, err) }) t.Run("no_field", func(t *testing.T) { @@ -1119,7 +1123,7 @@ func TestUpgradeSchema19to20(t *testing.T) { "statistics": yobj{}, } - err := upgradeSchema19to20(conf) + err := migrateTo20(conf) require.NoError(t, err) statsVal, ok := conf["statistics"] @@ -1176,7 +1180,7 @@ func TestUpgradeSchema20to21(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema20to21(tc.in) + err := migrateTo21(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -1246,7 +1250,7 @@ func TestUpgradeSchema21to22(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema21to22(tc.in) + err := migrateTo22(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -1299,7 +1303,7 @@ func TestUpgradeSchema22to23(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema22to23(tc.in) + err := migrateTo23(tc.in) require.NoError(t, err) assert.Equal(t, tc.want, tc.in) @@ -1358,24 +1362,287 @@ func TestUpgradeSchema23to24(t *testing.T) { "verbose": true, }, want: yobj{ - "log_file": "/test/path.log", - "log_max_backups": 1, - "log_max_size": 2, - "log_max_age": 3, - "log_compress": "", - "log_localtime": true, - "verbose": true, - "schema_version": newSchemaVer, + "log_compress": "", + "schema_version": newSchemaVer, }, - wantErrMsg: "unexpected type of log_compress: string", + wantErrMsg: `unexpected type of "log_compress": string`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema23to24(tc.in) + err := migrateTo24(tc.in) testutil.AssertErrorMsg(t, tc.wantErrMsg, err) assert.Equal(t, tc.want, tc.in) }) } } + +func TestUpgradeSchema24to25(t *testing.T) { + const newSchemaVer = 25 + + testCases := []struct { + in yobj + want yobj + name string + wantErrMsg string + }{{ + name: "empty", + in: yobj{}, + want: yobj{ + "schema_version": newSchemaVer, + }, + wantErrMsg: "", + }, { + name: "ok", + in: yobj{ + "http": yobj{ + "address": "0.0.0.0:3000", + "session_ttl": "720h", + }, + "debug_pprof": true, + }, + want: yobj{ + "http": yobj{ + "address": "0.0.0.0:3000", + "session_ttl": "720h", + "pprof": yobj{ + "enabled": true, + "port": 6060, + }, + }, + "schema_version": newSchemaVer, + }, + wantErrMsg: "", + }, { + name: "ok_disabled", + in: yobj{ + "http": yobj{ + "address": "0.0.0.0:3000", + "session_ttl": "720h", + }, + "debug_pprof": false, + }, + want: yobj{ + "http": yobj{ + "address": "0.0.0.0:3000", + "session_ttl": "720h", + "pprof": yobj{ + "enabled": false, + "port": 6060, + }, + }, + "schema_version": newSchemaVer, + }, + wantErrMsg: "", + }, { + name: "invalid", + in: yobj{ + "http": yobj{ + "address": "0.0.0.0:3000", + "session_ttl": "720h", + }, + "debug_pprof": 1, + }, + want: yobj{ + "http": yobj{ + "address": "0.0.0.0:3000", + "session_ttl": "720h", + }, + "debug_pprof": 1, + "schema_version": newSchemaVer, + }, + wantErrMsg: `unexpected type of "debug_pprof": int`, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := migrateTo25(tc.in) + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + + assert.Equal(t, tc.want, tc.in) + }) + } +} + +func TestUpgradeSchema25to26(t *testing.T) { + const newSchemaVer = 26 + + testCases := []struct { + in yobj + want yobj + name string + }{{ + name: "empty", + in: yobj{}, + want: yobj{ + "schema_version": newSchemaVer, + }, + }, { + name: "ok", + in: yobj{ + "dns": yobj{ + "filtering_enabled": true, + "filters_update_interval": 24, + "parental_enabled": false, + "safebrowsing_enabled": false, + "safebrowsing_cache_size": 1048576, + "safesearch_cache_size": 1048576, + "parental_cache_size": 1048576, + "safe_search": yobj{ + "enabled": false, + "bing": true, + "duckduckgo": true, + "google": true, + "pixabay": true, + "yandex": true, + "youtube": true, + }, + "rewrites": yarr{}, + "blocked_services": yobj{ + "schedule": yobj{ + "time_zone": "Local", + }, + "ids": yarr{}, + }, + "protection_enabled": true, + "blocking_mode": "custom_ip", + "blocking_ipv4": "1.2.3.4", + "blocking_ipv6": "1:2:3::4", + "blocked_response_ttl": 10, + "protection_disabled_until": nil, + "parental_block_host": "p.dns.adguard.com", + "safebrowsing_block_host": "s.dns.adguard.com", + }, + }, + want: yobj{ + "dns": yobj{}, + "filtering": yobj{ + "filtering_enabled": true, + "filters_update_interval": 24, + "parental_enabled": false, + "safebrowsing_enabled": false, + "safebrowsing_cache_size": 1048576, + "safesearch_cache_size": 1048576, + "parental_cache_size": 1048576, + "safe_search": yobj{ + "enabled": false, + "bing": true, + "duckduckgo": true, + "google": true, + "pixabay": true, + "yandex": true, + "youtube": true, + }, + "rewrites": yarr{}, + "blocked_services": yobj{ + "schedule": yobj{ + "time_zone": "Local", + }, + "ids": yarr{}, + }, + "protection_enabled": true, + "blocking_mode": "custom_ip", + "blocking_ipv4": "1.2.3.4", + "blocking_ipv6": "1:2:3::4", + "blocked_response_ttl": 10, + "protection_disabled_until": nil, + "parental_block_host": "p.dns.adguard.com", + "safebrowsing_block_host": "s.dns.adguard.com", + }, + "schema_version": newSchemaVer, + }, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := migrateTo26(tc.in) + require.NoError(t, err) + + assert.Equal(t, tc.want, tc.in) + }) + } +} + +func TestUpgradeSchema26to27(t *testing.T) { + const newSchemaVer = 27 + + testCases := []struct { + in yobj + want yobj + name string + }{{ + name: "empty", + in: yobj{}, + want: yobj{ + "schema_version": newSchemaVer, + }, + }, { + name: "single_dot", + in: yobj{ + "querylog": yobj{ + "ignored": yarr{ + ".", + }, + }, + "statistics": yobj{ + "ignored": yarr{ + ".", + }, + }, + }, + want: yobj{ + "querylog": yobj{ + "ignored": yarr{ + "|.^", + }, + }, + "statistics": yobj{ + "ignored": yarr{ + "|.^", + }, + }, + "schema_version": newSchemaVer, + }, + }, { + name: "mixed", + in: yobj{ + "querylog": yobj{ + "ignored": yarr{ + ".", + "example.com", + }, + }, + "statistics": yobj{ + "ignored": yarr{ + ".", + "example.org", + }, + }, + }, + want: yobj{ + "querylog": yobj{ + "ignored": yarr{ + "|.^", + "example.com", + }, + }, + "statistics": yobj{ + "ignored": yarr{ + "|.^", + "example.org", + }, + }, + "schema_version": newSchemaVer, + }, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := migrateTo27(tc.in) + require.NoError(t, err) + + assert.Equal(t, tc.want, tc.in) + }) + } +} diff --git a/internal/confmigrate/migrator.go b/internal/confmigrate/migrator.go new file mode 100644 index 00000000..636db690 --- /dev/null +++ b/internal/confmigrate/migrator.go @@ -0,0 +1,140 @@ +// Package confmigrate provides a way to upgrade the YAML configuration file. +package confmigrate + +import ( + "bytes" + "fmt" + + "github.com/AdguardTeam/golibs/log" + yaml "gopkg.in/yaml.v3" +) + +// LastSchemaVersion is the most recent schema version. +const LastSchemaVersion uint = 27 + +// Config is a the configuration for initializing a [Migrator]. +type Config struct { + // WorkingDir is an absolute path to the working directory of AdGuardHome. + WorkingDir string +} + +// Migrator performs the YAML configuration file migrations. +type Migrator struct { + // workingDir is an absolute path to the working directory of AdGuardHome. + workingDir string +} + +// New creates a new Migrator. +func New(cfg *Config) (m *Migrator) { + return &Migrator{ + workingDir: cfg.WorkingDir, + } +} + +// Migrate preforms necessary upgrade operations to upgrade file to target +// schema version, if needed. It returns the body of the upgraded config file, +// whether the file was upgraded, and an error, if any. If upgraded is false, +// the body is the same as the input. +func (m *Migrator) Migrate(body []byte, target uint) (newBody []byte, upgraded bool, err error) { + diskConf := yobj{} + err = yaml.Unmarshal(body, &diskConf) + if err != nil { + return body, false, fmt.Errorf("parsing config file for upgrade: %w", err) + } + + currentInt, _, err := fieldVal[int](diskConf, "schema_version") + if err != nil { + // Don't wrap the error, since it's informative enough as is. + return body, false, err + } + + current := uint(currentInt) + log.Debug("got schema version %v", current) + + if err = validateVersion(current, target); err != nil { + // Don't wrap the error, since it's informative enough as is. + return body, false, err + } else if current == target { + return body, false, nil + } + + if err = m.upgradeConfigSchema(current, target, diskConf); err != nil { + // Don't wrap the error, since it's informative enough as is. + return body, false, err + } + + buf := bytes.NewBuffer(newBody) + enc := yaml.NewEncoder(buf) + enc.SetIndent(2) + + if err = enc.Encode(diskConf); err != nil { + return body, false, fmt.Errorf("generating new config: %w", err) + } + + return buf.Bytes(), true, nil +} + +// validateVersion validates the current and desired schema versions. +func validateVersion(current, target uint) (err error) { + switch { + case current > target: + return fmt.Errorf("unknown current schema version %d", current) + case target > LastSchemaVersion: + return fmt.Errorf("unknown target schema version %d", target) + case target < current: + return fmt.Errorf("target schema version %d lower than current %d", target, current) + default: + return nil + } +} + +// migrateFunc is a function that upgrades a config and returns an error. +type migrateFunc = func(diskConf yobj) (err error) + +// upgradeConfigSchema upgrades the configuration schema in diskConf from +// current to target version. current must be less than target, and both must +// be non-negative and less or equal to [LastSchemaVersion]. +func (m *Migrator) upgradeConfigSchema(current, target uint, diskConf yobj) (err error) { + upgrades := [LastSchemaVersion]migrateFunc{ + 0: m.migrateTo1, + 1: m.migrateTo2, + 2: migrateTo3, + 3: migrateTo4, + 4: migrateTo5, + 5: migrateTo6, + 6: migrateTo7, + 7: migrateTo8, + 8: migrateTo9, + 9: migrateTo10, + 10: migrateTo11, + 11: migrateTo12, + 12: migrateTo13, + 13: migrateTo14, + 14: migrateTo15, + 15: migrateTo16, + 16: migrateTo17, + 17: migrateTo18, + 18: migrateTo19, + 19: migrateTo20, + 20: migrateTo21, + 21: migrateTo22, + 22: migrateTo23, + 23: migrateTo24, + 24: migrateTo25, + 25: migrateTo26, + 26: migrateTo27, + } + + for i, migrate := range upgrades[current:target] { + cur := current + uint(i) + next := current + uint(i) + 1 + + log.Printf("Upgrade yaml: %d to %d", cur, next) + + if err = migrate(diskConf); err != nil { + return fmt.Errorf("migrating schema %d to %d: %w", cur, next, err) + } + } + + return nil +} diff --git a/internal/confmigrate/migrator_test.go b/internal/confmigrate/migrator_test.go new file mode 100644 index 00000000..5cc8f3fb --- /dev/null +++ b/internal/confmigrate/migrator_test.go @@ -0,0 +1,208 @@ +package confmigrate_test + +import ( + "io/fs" + "os" + "path/filepath" + "testing" + + "github.com/AdguardTeam/AdGuardHome/internal/confmigrate" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/bcrypt" + yaml "gopkg.in/yaml.v3" +) + +// getField returns the value located at the given indexes in the given object. +// It fails the test if the value is not found or of the expected type. The +// indexes can be either strings or integers, and are interpreted as map keys or +// array indexes, respectively. +func getField[T any](t require.TestingT, obj any, indexes ...any) (val T) { + for _, index := range indexes { + switch index := index.(type) { + case string: + require.IsType(t, map[string]any(nil), obj) + typedObj := obj.(map[string]any) + + require.Contains(t, typedObj, index) + obj = typedObj[index] + case int: + require.IsType(t, []any(nil), obj) + typedObj := obj.([]any) + + require.Less(t, index, len(typedObj)) + obj = typedObj[index] + default: + t.Errorf("unexpected index type: %T", index) + t.FailNow() + } + } + + require.IsType(t, val, obj) + + return obj.(T) +} + +// testdata is a virtual filesystem containing test data. +var testdata = os.DirFS("testdata") + +func TestMigrateConfig_Migrate(t *testing.T) { + const ( + inputFileName = "input.yml" + outputFileName = "output.yml" + ) + + testCases := []struct { + yamlEqFunc func(t require.TestingT, expected, actual string, msgAndArgs ...any) + name string + targetVersion uint + }{{ + yamlEqFunc: require.YAMLEq, + name: "v1", + targetVersion: 1, + }, { + yamlEqFunc: require.YAMLEq, + name: "v2", + targetVersion: 2, + }, { + yamlEqFunc: require.YAMLEq, + name: "v3", + targetVersion: 3, + }, { + yamlEqFunc: require.YAMLEq, + name: "v4", + targetVersion: 4, + }, { + // Compare passwords separately because bcrypt hashes those with a + // different salt every time. + yamlEqFunc: func(t require.TestingT, expected, actual string, msgAndArgs ...any) { + if h, ok := t.(interface{ Helper() }); ok { + h.Helper() + } + + var want, got map[string]any + err := yaml.Unmarshal([]byte(expected), &want) + require.NoError(t, err) + + err = yaml.Unmarshal([]byte(actual), &got) + require.NoError(t, err) + + gotPass := getField[string](t, got, "users", 0, "password") + wantPass := getField[string](t, want, "users", 0, "password") + require.NoError(t, bcrypt.CompareHashAndPassword([]byte(gotPass), []byte(wantPass))) + + delete(getField[map[string]any](t, got, "users", 0), "password") + delete(getField[map[string]any](t, want, "users", 0), "password") + + require.Equal(t, want, got, msgAndArgs...) + }, + name: "v5", + targetVersion: 5, + }, { + yamlEqFunc: require.YAMLEq, + name: "v6", + targetVersion: 6, + }, { + yamlEqFunc: require.YAMLEq, + name: "v7", + targetVersion: 7, + }, { + yamlEqFunc: require.YAMLEq, + name: "v8", + targetVersion: 8, + }, { + yamlEqFunc: require.YAMLEq, + name: "v9", + targetVersion: 9, + }, { + yamlEqFunc: require.YAMLEq, + name: "v10", + targetVersion: 10, + }, { + yamlEqFunc: require.YAMLEq, + name: "v11", + targetVersion: 11, + }, { + yamlEqFunc: require.YAMLEq, + name: "v12", + targetVersion: 12, + }, { + yamlEqFunc: require.YAMLEq, + name: "v13", + targetVersion: 13, + }, { + yamlEqFunc: require.YAMLEq, + name: "v14", + targetVersion: 14, + }, { + yamlEqFunc: require.YAMLEq, + name: "v15", + targetVersion: 15, + }, { + yamlEqFunc: require.YAMLEq, + name: "v16", + targetVersion: 16, + }, { + yamlEqFunc: require.YAMLEq, + name: "v17", + targetVersion: 17, + }, { + yamlEqFunc: require.YAMLEq, + name: "v18", + targetVersion: 18, + }, { + yamlEqFunc: require.YAMLEq, + name: "v19", + targetVersion: 19, + }, { + yamlEqFunc: require.YAMLEq, + name: "v20", + targetVersion: 20, + }, { + yamlEqFunc: require.YAMLEq, + name: "v21", + targetVersion: 21, + }, { + yamlEqFunc: require.YAMLEq, + name: "v22", + targetVersion: 22, + }, { + yamlEqFunc: require.YAMLEq, + name: "v23", + targetVersion: 23, + }, { + yamlEqFunc: require.YAMLEq, + name: "v24", + targetVersion: 24, + }, { + yamlEqFunc: require.YAMLEq, + name: "v25", + targetVersion: 25, + }, { + yamlEqFunc: require.YAMLEq, + name: "v26", + targetVersion: 26, + }, { + yamlEqFunc: require.YAMLEq, + name: "v27", + targetVersion: 27, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + body, err := fs.ReadFile(testdata, filepath.Join(t.Name(), inputFileName)) + require.NoError(t, err) + + wantBody, err := fs.ReadFile(testdata, filepath.Join(t.Name(), outputFileName)) + require.NoError(t, err) + + migrator := confmigrate.New(&confmigrate.Config{ + WorkingDir: t.Name(), + }) + newBody, upgraded, err := migrator.Migrate(body, tc.targetVersion) + require.NoError(t, err) + require.True(t, upgraded) + + tc.yamlEqFunc(t, string(wantBody), string(newBody)) + }) + } +} diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/input.yml new file mode 100644 index 00000000..1598a93d --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/input.yml @@ -0,0 +1,31 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +auth_name: testuser +auth_pass: testpassword +coredns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/output.yml new file mode 100644 index 00000000..37990a88 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v1/output.yml @@ -0,0 +1,32 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +auth_name: testuser +auth_pass: testpassword +coredns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +schema_version: 1 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/input.yml new file mode 100644 index 00000000..edb6e8d2 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/input.yml @@ -0,0 +1,60 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + local_domain_name: local + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 9 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/output.yml new file mode 100644 index 00000000..7aa57b72 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v10/output.yml @@ -0,0 +1,60 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + local_domain_name: local + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 10 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/input.yml new file mode 100644 index 00000000..d2b0b076 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/input.yml @@ -0,0 +1,61 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + local_domain_name: local + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 10 +user_rules: [] +rlimit_nofile: 123 diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/output.yml new file mode 100644 index 00000000..adb89c21 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v11/output.yml @@ -0,0 +1,64 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + local_domain_name: local + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 11 +user_rules: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/input.yml new file mode 100644 index 00000000..ebc8c883 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/input.yml @@ -0,0 +1,65 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + local_domain_name: local + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + querylog_interval: 30 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 11 +user_rules: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/output.yml new file mode 100644 index 00000000..56aaede5 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v12/output.yml @@ -0,0 +1,65 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + local_domain_name: local + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + querylog_interval: 720h + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 12 +user_rules: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/input.yml new file mode 100644 index 00000000..56aaede5 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/input.yml @@ -0,0 +1,65 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + local_domain_name: local + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + querylog_interval: 720h + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 12 +user_rules: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/output.yml new file mode 100644 index 00000000..09993bfa --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v13/output.yml @@ -0,0 +1,65 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + querylog_interval: 720h + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 13 +user_rules: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/input.yml new file mode 100644 index 00000000..ab7ca6df --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/input.yml @@ -0,0 +1,66 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + querylog_interval: 720h + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + resolve_clients: true +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 13 +user_rules: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/output.yml new file mode 100644 index 00000000..4f9884a5 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v14/output.yml @@ -0,0 +1,72 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + querylog_interval: 720h + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 14 +user_rules: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/input.yml new file mode 100644 index 00000000..42ecb2b3 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/input.yml @@ -0,0 +1,74 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + querylog_file_enabled: true + querylog_interval: 720h + querylog_size_memory: 1000 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 14 +user_rules: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/output.yml new file mode 100644 index 00000000..258848f3 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v15/output.yml @@ -0,0 +1,76 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 15 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/input.yml new file mode 100644 index 00000000..8d55775c --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/input.yml @@ -0,0 +1,77 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + statistics_interval: 10 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 15 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/output.yml new file mode 100644 index 00000000..79144e60 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v16/output.yml @@ -0,0 +1,80 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 16 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 10 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/input.yml new file mode 100644 index 00000000..5114414f --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/input.yml @@ -0,0 +1,81 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: true +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 16 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 10 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/output.yml new file mode 100644 index 00000000..7b962db0 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v17/output.yml @@ -0,0 +1,84 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 17 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 10 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/input.yml new file mode 100644 index 00000000..7b962db0 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/input.yml @@ -0,0 +1,84 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 17 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 10 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/output.yml new file mode 100644 index 00000000..168db4e6 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v18/output.yml @@ -0,0 +1,91 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 18 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 10 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/input.yml new file mode 100644 index 00000000..d1b34a3e --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/input.yml @@ -0,0 +1,91 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: true + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 18 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 10 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/output.yml new file mode 100644 index 00000000..ef60d514 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v19/output.yml @@ -0,0 +1,98 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 19 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 10 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/input.yml new file mode 100644 index 00000000..804b0259 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/input.yml @@ -0,0 +1,34 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +auth_name: testuser +auth_pass: testpassword +coredns: + bind_host: 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +schema_version: 1 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/output.yml new file mode 100644 index 00000000..d7676d96 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v2/output.yml @@ -0,0 +1,34 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +auth_name: testuser +auth_pass: testpassword +dns: + bind_host: 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +schema_version: 2 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/input.yml new file mode 100644 index 00000000..ef60d514 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/input.yml @@ -0,0 +1,98 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 19 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 10 + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/output.yml new file mode 100644 index 00000000..22823490 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v20/output.yml @@ -0,0 +1,98 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 20 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/input.yml new file mode 100644 index 00000000..14d27796 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/input.yml @@ -0,0 +1,100 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 20 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/output.yml new file mode 100644 index 00000000..9b893883 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v21/output.yml @@ -0,0 +1,103 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 21 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/input.yml new file mode 100644 index 00000000..887a837e --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/input.yml @@ -0,0 +1,105 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 21 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/output.yml new file mode 100644 index 00000000..ec568781 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v22/output.yml @@ -0,0 +1,108 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 22 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/input.yml new file mode 100644 index 00000000..aea80065 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/input.yml @@ -0,0 +1,109 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +web_session_ttl: 3 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 22 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/output.yml new file mode 100644 index 00000000..f8c41b2b --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v23/output.yml @@ -0,0 +1,109 @@ +http: + address: 127.0.0.1:3000 + session_ttl: 3h +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 23 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/input.yml new file mode 100644 index 00000000..fd3ba93d --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/input.yml @@ -0,0 +1,116 @@ +http: + address: 127.0.0.1:3000 + session_ttl: 3h +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 23 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: "" + rlimit_nofile: 123 + user: "" +log_file: "" +log_max_backups: 0 +log_max_size: 100 +log_max_age: 3 +log_compress: true +log_localtime: false +verbose: true diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/output.yml new file mode 100644 index 00000000..4b3063fb --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v24/output.yml @@ -0,0 +1,117 @@ +http: + address: 127.0.0.1:3000 + session_ttl: 3h +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 24 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' +log: + file: "" + max_backups: 0 + max_size: 100 + max_age: 3 + compress: true + local_time: false + verbose: true diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/input.yml new file mode 100644 index 00000000..ba70b5e8 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/input.yml @@ -0,0 +1,118 @@ +http: + address: 127.0.0.1:3000 + session_ttl: 3h +debug_pprof: true +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 24 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' +log: + file: "" + max_backups: 0 + max_size: 100 + max_age: 3 + compress: true + local_time: false + verbose: true diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/output.yml new file mode 100644 index 00000000..58511b3e --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v25/output.yml @@ -0,0 +1,120 @@ +http: + address: 127.0.0.1:3000 + session_ttl: 3h + pprof: + enabled: true + port: 6060 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 25 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' +log: + file: "" + max_backups: 0 + max_size: 100 + max_age: 3 + compress: true + local_time: false + verbose: true diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/input.yml new file mode 100644 index 00000000..58511b3e --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/input.yml @@ -0,0 +1,120 @@ +http: + address: 127.0.0.1:3000 + session_ttl: 3h + pprof: + enabled: true + port: 6060 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" + blocked_services: + schedule: + time_zone: Local + ids: + - 500px +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 25 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' +log: + file: "" + max_backups: 0 + max_size: 100 + max_age: 3 + compress: true + local_time: false + verbose: true diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/output.yml new file mode 100644 index 00000000..253df602 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v26/output.yml @@ -0,0 +1,121 @@ +http: + address: 127.0.0.1:3000 + session_ttl: 3h + pprof: + enabled: true + port: 6060 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + parental_sensitivity: 0 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filtering: + filtering_enabled: true + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + protection_enabled: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + blocked_response_ttl: 10 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 26 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: [] +statistics: + enabled: true + interval: 240h + ignored: [] +os: + group: '' + rlimit_nofile: 123 + user: '' +log: + file: "" + max_backups: 0 + max_size: 100 + max_age: 3 + compress: true + local_time: false + verbose: true diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/input.yml new file mode 100644 index 00000000..6472343d --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/input.yml @@ -0,0 +1,123 @@ +http: + address: 127.0.0.1:3000 + session_ttl: 3h + pprof: + enabled: true + port: 6060 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + parental_sensitivity: 0 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filtering: + filtering_enabled: true + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + protection_enabled: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + blocked_response_ttl: 10 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 26 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: + - '.' +statistics: + enabled: true + interval: 240h + ignored: + - '.' +os: + group: '' + rlimit_nofile: 123 + user: '' +log: + file: "" + max_backups: 0 + max_size: 100 + max_age: 3 + compress: true + local_time: false + verbose: true diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/output.yml new file mode 100644 index 00000000..e25b02c7 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v27/output.yml @@ -0,0 +1,123 @@ +http: + address: 127.0.0.1:3000 + session_ttl: 3h + pprof: + enabled: true + port: 6060 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + parental_sensitivity: 0 + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + - quic://8.8.8.8:784 + bootstrap_dns: + - 8.8.8.8:53 + edns_client_subnet: + enabled: true + use_custom: false + custom_ip: "" +filtering: + filtering_enabled: true + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: false + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + protection_enabled: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + blocked_response_ttl: 10 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: + persistent: + - name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safe_search: + enabled: true + bing: true + duckduckgo: true + google: true + pixabay: true + yandex: true + youtube: true + blocked_services: + schedule: + time_zone: Local + ids: + - 500px + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true +dhcp: + enabled: false + interface_name: vboxnet0 + local_domain_name: local + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 27 +user_rules: [] +querylog: + enabled: true + file_enabled: true + interval: 720h + size_memory: 1000 + ignored: + - '|.^' +statistics: + enabled: true + interval: 240h + ignored: + - '|.^' +os: + group: '' + rlimit_nofile: 123 + user: '' +log: + file: "" + max_backups: 0 + max_size: 100 + max_age: 3 + compress: true + local_time: false + verbose: true diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/input.yml new file mode 100644 index 00000000..1c1d25c1 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/input.yml @@ -0,0 +1,33 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +auth_name: testuser +auth_pass: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +schema_version: 2 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/output.yml new file mode 100644 index 00000000..86cb22a5 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v3/output.yml @@ -0,0 +1,34 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +auth_name: testuser +auth_pass: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +schema_version: 3 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/input.yml new file mode 100644 index 00000000..f43acf12 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/input.yml @@ -0,0 +1,43 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +auth_name: testuser +auth_pass: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ip: 127.0.0.1 + mac: "" + use_global_settings: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +schema_version: 3 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/output.yml new file mode 100644 index 00000000..92e8085d --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v4/output.yml @@ -0,0 +1,44 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +auth_name: testuser +auth_pass: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ip: 127.0.0.1 + mac: "" + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +schema_version: 4 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/input.yml new file mode 100644 index 00000000..92e8085d --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/input.yml @@ -0,0 +1,44 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +auth_name: testuser +auth_pass: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ip: 127.0.0.1 + mac: "" + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +schema_version: 4 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/output.yml new file mode 100644 index 00000000..032b3081 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v5/output.yml @@ -0,0 +1,45 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ip: 127.0.0.1 + mac: "" + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +schema_version: 5 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/input.yml new file mode 100644 index 00000000..3fc6794f --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/input.yml @@ -0,0 +1,45 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ip: 127.0.0.1 + mac: aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +schema_version: 5 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/output.yml new file mode 100644 index 00000000..8ea2379a --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v6/output.yml @@ -0,0 +1,48 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + ip: 127.0.0.1 + mac: aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +schema_version: 6 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/input.yml new file mode 100644 index 00000000..d6a1e5f7 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/input.yml @@ -0,0 +1,53 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 6 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/output.yml new file mode 100644 index 00000000..c93fbc6a --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v7/output.yml @@ -0,0 +1,54 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 7 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/input.yml new file mode 100644 index 00000000..a940ccad --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/input.yml @@ -0,0 +1,57 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_host: 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 7 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/output.yml new file mode 100644 index 00000000..82be0734 --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v8/output.yml @@ -0,0 +1,58 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 8 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/input.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/input.yml new file mode 100644 index 00000000..f713e52c --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/input.yml @@ -0,0 +1,59 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + autohost_tld: local + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 8 +user_rules: [] diff --git a/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/output.yml b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/output.yml new file mode 100644 index 00000000..25a1893d --- /dev/null +++ b/internal/confmigrate/testdata/TestMigrateConfig_Migrate/v9/output.yml @@ -0,0 +1,59 @@ +bind_host: 127.0.0.1 +bind_port: 3000 +users: +- name: testuser + password: testpassword +dns: + bind_hosts: + - 127.0.0.1 + port: 53 + local_domain_name: local + protection_enabled: true + filtering_enabled: true + safebrowsing_enabled: false + safesearch_enabled: false + parental_enabled: false + parental_sensitivity: 0 + blocked_response_ttl: 10 + querylog_enabled: true + upstream_dns: + - tls://1.1.1.1 + - tls://1.0.0.1 + bootstrap_dns: + - 8.8.8.8:53 +filters: +- url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: "" + enabled: true +- url: https://adaway.org/hosts.txt + name: AdAway + enabled: false +- url: https://hosts-file.net/ad_servers.txt + name: hpHosts - Ad and Tracking servers only + enabled: false +- url: http://www.malwaredomainlist.com/hostslist/hosts.txt + name: MalwareDomainList.com Hosts List + enabled: false +clients: +- name: localhost + ids: + - 127.0.0.1 + - aa:aa:aa:aa:aa:aa + use_global_settings: true + use_global_blocked_services: true + filtering_enabled: false + parental_enabled: false + safebrowsing_enabled: false + safesearch_enabled: false +dhcp: + enabled: false + interface_name: vboxnet0 + dhcpv4: + gateway_ip: 192.168.0.1 + subnet_mask: 255.255.255.0 + range_start: 192.168.0.10 + range_end: 192.168.0.250 + lease_duration: 1234 + icmp_timeout_msec: 10 +schema_version: 9 +user_rules: [] diff --git a/internal/confmigrate/v1.go b/internal/confmigrate/v1.go new file mode 100644 index 00000000..780c0273 --- /dev/null +++ b/internal/confmigrate/v1.go @@ -0,0 +1,35 @@ +package confmigrate + +import ( + "os" + "path/filepath" + + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/log" +) + +// migrateTo1 performs the following changes: +// +// # BEFORE: +// # … +// +// # AFTER: +// 'schema_version': 1 +// # … +// +// It also deletes the unused dnsfilter.txt file, since the following versions +// store filters in data/filters/. +func (m *Migrator) migrateTo1(diskConf yobj) (err error) { + diskConf["schema_version"] = 1 + + dnsFilterPath := filepath.Join(m.workingDir, "dnsfilter.txt") + log.Printf("deleting %s as we don't need it anymore", dnsFilterPath) + err = os.Remove(dnsFilterPath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + log.Info("warning: %s", err) + + // Go on. + } + + return nil +} diff --git a/internal/confmigrate/v10.go b/internal/confmigrate/v10.go new file mode 100644 index 00000000..0cf10c2f --- /dev/null +++ b/internal/confmigrate/v10.go @@ -0,0 +1,118 @@ +package confmigrate + +import ( + "fmt" + "net/url" + "strconv" + "strings" + + "github.com/AdguardTeam/golibs/netutil" +) + +// migrateTo10 performs the following changes: +// +// # BEFORE: +// 'schema_version': 9 +// 'dns': +// 'upstream_dns': +// - 'quic://some-upstream.com' +// 'local_ptr_upstreams': +// - 'quic://some-upstream.com' +// # … +// # … +// +// # AFTER: +// 'schema_version': 10 +// 'dns': +// 'upstream_dns': +// - 'quic://some-upstream.com:784' +// 'local_ptr_upstreams': +// - 'quic://some-upstream.com:784' +// # … +// # … +func migrateTo10(diskConf yobj) (err error) { + diskConf["schema_version"] = 10 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + const quicPort = 784 + + ups, ok, err := fieldVal[yarr](dns, "upstream_dns") + if err != nil { + return err + } else if ok { + if err = addQUICPorts(ups, quicPort); err != nil { + return err + } + + dns["upstream_dns"] = ups + } + + ups, ok, err = fieldVal[yarr](dns, "local_ptr_upstreams") + if err != nil { + return err + } else if ok { + if err = addQUICPorts(ups, quicPort); err != nil { + return err + } + + dns["local_ptr_upstreams"] = ups + } + + return nil +} + +// addQUICPorts inserts a port into each QUIC upstream's hostname in ups if +// those are missing. +func addQUICPorts(ups yarr, port int) (err error) { + for i, uVal := range ups { + u, ok := uVal.(string) + if !ok { + return fmt.Errorf("unexpected type of upstream field: %T", uVal) + } + + ups[i] = addQUICPort(u, port) + } + + return nil +} + +// addQUICPort inserts a port into QUIC upstream's hostname if it is missing. +func addQUICPort(ups string, port int) (withPort string) { + if ups == "" || ups[0] == '#' { + return ups + } + + var doms string + withPort = ups + if strings.HasPrefix(ups, "[/") { + domsAndUps := strings.Split(strings.TrimPrefix(ups, "[/"), "/]") + if len(domsAndUps) != 2 { + return ups + } + + doms, withPort = "[/"+domsAndUps[0]+"/]", domsAndUps[1] + } + + if !strings.Contains(withPort, "://") { + return ups + } + + upsURL, err := url.Parse(withPort) + if err != nil || upsURL.Scheme != "quic" { + return ups + } + + var host string + host, err = netutil.SplitHost(upsURL.Host) + if err != nil || host != upsURL.Host { + return ups + } + + upsURL.Host = strings.Join([]string{host, strconv.Itoa(port)}, ":") + + return doms + upsURL.String() +} diff --git a/internal/confmigrate/v11.go b/internal/confmigrate/v11.go new file mode 100644 index 00000000..d93fbcd8 --- /dev/null +++ b/internal/confmigrate/v11.go @@ -0,0 +1,33 @@ +package confmigrate + +// migrateTo11 performs the following changes: +// +// # BEFORE: +// 'schema_version': 10 +// 'rlimit_nofile': 42 +// # … +// +// # AFTER: +// 'schema_version': 11 +// 'os': +// 'group': '' +// 'rlimit_nofile': 42 +// 'user': '' +// # … +func migrateTo11(diskConf yobj) (err error) { + diskConf["schema_version"] = 11 + + rlimit, _, err := fieldVal[int](diskConf, "rlimit_nofile") + if err != nil { + return err + } + + delete(diskConf, "rlimit_nofile") + diskConf["os"] = yobj{ + "group": "", + "rlimit_nofile": rlimit, + "user": "", + } + + return nil +} diff --git a/internal/confmigrate/v12.go b/internal/confmigrate/v12.go new file mode 100644 index 00000000..6703561a --- /dev/null +++ b/internal/confmigrate/v12.go @@ -0,0 +1,43 @@ +package confmigrate + +import ( + "time" + + "github.com/AdguardTeam/golibs/timeutil" +) + +// migrateTo12 performs the following changes: +// +// # BEFORE: +// 'schema_version': 11 +// 'querylog_interval': 90 +// # … +// +// # AFTER: +// 'schema_version': 12 +// 'querylog_interval': '2160h' +// # … +func migrateTo12(diskConf yobj) (err error) { + diskConf["schema_version"] = 12 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + const field = "querylog_interval" + + qlogIvl, ok, err := fieldVal[int](dns, field) + if !ok { + if err != nil { + return err + } + + // Set the initial value from home.initConfig function. + qlogIvl = 90 + } + + dns[field] = timeutil.Duration{Duration: time.Duration(qlogIvl) * timeutil.Day} + + return nil +} diff --git a/internal/confmigrate/v13.go b/internal/confmigrate/v13.go new file mode 100644 index 00000000..a69592bf --- /dev/null +++ b/internal/confmigrate/v13.go @@ -0,0 +1,32 @@ +package confmigrate + +// migrateTo13 performs the following changes: +// +// # BEFORE: +// 'schema_version': 12 +// 'dns': +// 'local_domain_name': 'lan' +// # … +// # … +// +// # AFTER: +// 'schema_version': 13 +// 'dhcp': +// 'local_domain_name': 'lan' +// # … +// # … +func migrateTo13(diskConf yobj) (err error) { + diskConf["schema_version"] = 13 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + dhcp, ok, err := fieldVal[yobj](diskConf, "dhcp") + if !ok { + return err + } + + return moveSameVal[string](dns, dhcp, "local_domain_name") +} diff --git a/internal/confmigrate/v14.go b/internal/confmigrate/v14.go new file mode 100644 index 00000000..d259a2a6 --- /dev/null +++ b/internal/confmigrate/v14.go @@ -0,0 +1,62 @@ +package confmigrate + +// migrateTo14 performs the following changes: +// +// # BEFORE: +// 'schema_version': 13 +// 'dns': +// 'resolve_clients': true +// # … +// 'clients': +// - 'name': 'client-name' +// # … +// # … +// +// # AFTER: +// 'schema_version': 14 +// 'dns': +// # … +// 'clients': +// 'persistent': +// - 'name': 'client-name' +// # … +// 'runtime_sources': +// 'whois': true +// 'arp': true +// 'rdns': true +// 'dhcp': true +// 'hosts': true +// # … +func migrateTo14(diskConf yobj) (err error) { + diskConf["schema_version"] = 14 + + persistent, ok, err := fieldVal[yarr](diskConf, "clients") + if !ok { + if err != nil { + return err + } + + persistent = yarr{} + } + + runtimeClients := yobj{ + "whois": true, + "arp": true, + "rdns": false, + "dhcp": true, + "hosts": true, + } + diskConf["clients"] = yobj{ + "persistent": persistent, + "runtime_sources": runtimeClients, + } + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if err != nil { + return err + } else if !ok { + return nil + } + + return moveVal[bool](dns, runtimeClients, "resolve_clients", "rdns") +} diff --git a/internal/confmigrate/v15.go b/internal/confmigrate/v15.go new file mode 100644 index 00000000..904ef110 --- /dev/null +++ b/internal/confmigrate/v15.go @@ -0,0 +1,52 @@ +package confmigrate + +// migrateTo15 performs the following changes: +// +// # BEFORE: +// 'schema_version': 14 +// 'dns': +// # … +// 'querylog_enabled': true +// 'querylog_file_enabled': true +// 'querylog_interval': '2160h' +// 'querylog_size_memory': 1000 +// 'querylog': +// # … +// # … +// +// # AFTER: +// 'schema_version': 15 +// 'dns': +// # … +// 'querylog': +// 'enabled': true +// 'file_enabled': true +// 'interval': '2160h' +// 'size_memory': 1000 +// 'ignored': [] +// # … +// # … +func migrateTo15(diskConf yobj) (err error) { + diskConf["schema_version"] = 15 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + qlog := map[string]any{ + "ignored": yarr{}, + "enabled": true, + "file_enabled": true, + "interval": "2160h", + "size_memory": 1000, + } + diskConf["querylog"] = qlog + + return coalesceError( + moveVal[bool](dns, qlog, "querylog_enabled", "enabled"), + moveVal[bool](dns, qlog, "querylog_file_enabled", "file_enabled"), + moveVal[any](dns, qlog, "querylog_interval", "interval"), + moveVal[int](dns, qlog, "querylog_size_memory", "size_memory"), + ) +} diff --git a/internal/confmigrate/v16.go b/internal/confmigrate/v16.go new file mode 100644 index 00000000..b174d4cb --- /dev/null +++ b/internal/confmigrate/v16.go @@ -0,0 +1,78 @@ +package confmigrate + +// migrateTo16 performs the following changes: +// +// # BEFORE: +// 'schema_version': 15 +// 'dns': +// # … +// 'statistics_interval': 1 +// 'statistics': +// # … +// # … +// +// # AFTER: +// 'schema_version': 16 +// 'dns': +// # … +// 'statistics': +// 'enabled': true +// 'interval': 1 +// 'ignored': [] +// # … +// # … +// +// If statistics were disabled: +// +// # BEFORE: +// 'schema_version': 15 +// 'dns': +// # … +// 'statistics_interval': 0 +// 'statistics': +// # … +// # … +// +// # AFTER: +// 'schema_version': 16 +// 'dns': +// # … +// 'statistics': +// 'enabled': false +// 'interval': 1 +// 'ignored': [] +// # … +// # … +func migrateTo16(diskConf yobj) (err error) { + diskConf["schema_version"] = 16 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + stats := yobj{ + "enabled": true, + "interval": 1, + "ignored": yarr{}, + } + diskConf["statistics"] = stats + + const field = "statistics_interval" + + statsIvl, ok, err := fieldVal[int](dns, field) + if !ok { + return err + } + + if statsIvl == 0 { + // Set the interval to the default value of one day to make sure + // that it passes the validations. + stats["enabled"] = false + } else { + stats["interval"] = statsIvl + } + delete(dns, field) + + return nil +} diff --git a/internal/confmigrate/v17.go b/internal/confmigrate/v17.go new file mode 100644 index 00000000..a3a04460 --- /dev/null +++ b/internal/confmigrate/v17.go @@ -0,0 +1,39 @@ +package confmigrate + +// migrateTo17 performs the following changes: +// +// # BEFORE: +// 'schema_version': 16 +// 'dns': +// 'edns_client_subnet': false +// # … +// # … +// +// # AFTER: +// 'schema_version': 17 +// 'dns': +// 'edns_client_subnet': +// 'enabled': false +// 'use_custom': false +// 'custom_ip': "" +// # … +// # … +func migrateTo17(diskConf yobj) (err error) { + diskConf["schema_version"] = 17 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + const field = "edns_client_subnet" + + enabled, _, _ := fieldVal[bool](dns, field) + dns[field] = yobj{ + "enabled": enabled, + "use_custom": false, + "custom_ip": "", + } + + return nil +} diff --git a/internal/confmigrate/v18.go b/internal/confmigrate/v18.go new file mode 100644 index 00000000..28c55723 --- /dev/null +++ b/internal/confmigrate/v18.go @@ -0,0 +1,45 @@ +package confmigrate + +// migrateTo18 performs the following changes: +// +// # BEFORE: +// 'schema_version': 17 +// 'dns': +// 'safesearch_enabled': true +// # … +// # … +// +// # AFTER: +// 'schema_version': 18 +// 'dns': +// 'safe_search': +// 'enabled': true +// 'bing': true +// 'duckduckgo': true +// 'google': true +// 'pixabay': true +// 'yandex': true +// 'youtube': true +// # … +// # … +func migrateTo18(diskConf yobj) (err error) { + diskConf["schema_version"] = 18 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + safeSearch := yobj{ + "enabled": true, + "bing": true, + "duckduckgo": true, + "google": true, + "pixabay": true, + "yandex": true, + "youtube": true, + } + dns["safe_search"] = safeSearch + + return moveVal[bool](dns, safeSearch, "safesearch_enabled", "enabled") +} diff --git a/internal/confmigrate/v19.go b/internal/confmigrate/v19.go new file mode 100644 index 00000000..6fbb6d4f --- /dev/null +++ b/internal/confmigrate/v19.go @@ -0,0 +1,72 @@ +package confmigrate + +import "github.com/AdguardTeam/golibs/log" + +// migrateTo19 performs the following changes: +// +// # BEFORE: +// 'schema_version': 18 +// 'clients': +// 'persistent': +// - 'name': 'client-name' +// 'safesearch_enabled': true +// # … +// # … +// # … +// +// # AFTER: +// 'schema_version': 19 +// 'clients': +// 'persistent': +// - 'name': 'client-name' +// 'safe_search': +// 'enabled': true +// 'bing': true +// 'duckduckgo': true +// 'google': true +// 'pixabay': true +// 'yandex': true +// 'youtube': true +// # … +// # … +// # … +func migrateTo19(diskConf yobj) (err error) { + diskConf["schema_version"] = 19 + + clients, ok, err := fieldVal[yobj](diskConf, "clients") + if !ok { + return err + } + + persistent, ok, _ := fieldVal[yarr](clients, "persistent") + if !ok { + return nil + } + + for _, p := range persistent { + var c yobj + c, ok = p.(yobj) + if !ok { + continue + } + + safeSearch := yobj{ + "enabled": true, + "bing": true, + "duckduckgo": true, + "google": true, + "pixabay": true, + "yandex": true, + "youtube": true, + } + + err = moveVal[bool](c, safeSearch, "safesearch_enabled", "enabled") + if err != nil { + log.Debug("migrating to version 19: %s", err) + } + + c["safe_search"] = safeSearch + } + + return nil +} diff --git a/internal/confmigrate/v2.go b/internal/confmigrate/v2.go new file mode 100644 index 00000000..1faff87f --- /dev/null +++ b/internal/confmigrate/v2.go @@ -0,0 +1,37 @@ +package confmigrate + +import ( + "os" + "path/filepath" + + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/log" +) + +// migrateTo2 performs the following changes: +// +// # BEFORE: +// 'schema_version': 1 +// 'coredns': +// # … +// +// # AFTER: +// 'schema_version': 2 +// 'dns': +// # … +// +// It also deletes the Corefile file, since it isn't used anymore. +func (m *Migrator) migrateTo2(diskConf yobj) (err error) { + diskConf["schema_version"] = 2 + + coreFilePath := filepath.Join(m.workingDir, "Corefile") + log.Printf("deleting %s as we don't need it anymore", coreFilePath) + err = os.Remove(coreFilePath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + log.Info("warning: %s", err) + + // Go on. + } + + return moveVal[any](diskConf, diskConf, "coredns", "dns") +} diff --git a/internal/confmigrate/v20.go b/internal/confmigrate/v20.go new file mode 100644 index 00000000..e86c36c7 --- /dev/null +++ b/internal/confmigrate/v20.go @@ -0,0 +1,44 @@ +package confmigrate + +import ( + "time" + + "github.com/AdguardTeam/golibs/timeutil" +) + +// migrateTo20 performs the following changes: +// +// # BEFORE: +// 'schema_version': 19 +// 'statistics': +// 'interval': 1 +// # … +// # … +// +// # AFTER: +// 'schema_version': 20 +// 'statistics': +// 'interval': 24h +// # … +// # … +func migrateTo20(diskConf yobj) (err error) { + diskConf["schema_version"] = 20 + + stats, ok, err := fieldVal[yobj](diskConf, "statistics") + if !ok { + return err + } + + const field = "interval" + + ivl, ok, err := fieldVal[int](stats, field) + if err != nil { + return err + } else if !ok || ivl == 0 { + ivl = 1 + } + + stats[field] = timeutil.Duration{Duration: time.Duration(ivl) * timeutil.Day} + + return nil +} diff --git a/internal/confmigrate/v21.go b/internal/confmigrate/v21.go new file mode 100644 index 00000000..afda11bf --- /dev/null +++ b/internal/confmigrate/v21.go @@ -0,0 +1,49 @@ +package confmigrate + +// migrateTo21 performs the following changes: +// +// # BEFORE: +// 'schema_version': 20 +// 'dns': +// 'blocked_services': +// - 'svc_name' +// - # … +// # … +// # … +// +// # AFTER: +// 'schema_version': 21 +// 'dns': +// 'blocked_services': +// 'ids': +// - 'svc_name' +// - # … +// 'schedule': +// 'time_zone': 'Local' +// # … +// # … +func migrateTo21(diskConf yobj) (err error) { + diskConf["schema_version"] = 21 + + const field = "blocked_services" + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + svcs := yobj{ + "schedule": yobj{ + "time_zone": "Local", + }, + } + + err = moveVal[yarr](dns, svcs, field, "ids") + if err != nil { + return err + } + + dns[field] = svcs + + return nil +} diff --git a/internal/confmigrate/v22.go b/internal/confmigrate/v22.go new file mode 100644 index 00000000..4d5de732 --- /dev/null +++ b/internal/confmigrate/v22.go @@ -0,0 +1,72 @@ +package confmigrate + +import ( + "fmt" +) + +// migrateTo22 performs the following changes: +// +// # BEFORE: +// 'schema_version': 21 +// 'persistent': +// - 'name': 'client_name' +// 'blocked_services': +// - 'svc_name' +// - # … +// # … +// # … +// # … +// +// # AFTER: +// 'schema_version': 22 +// 'persistent': +// - 'name': 'client_name' +// 'blocked_services': +// 'ids': +// - 'svc_name' +// - # … +// 'schedule': +// 'time_zone': 'Local' +// # … +// # … +// # … +func migrateTo22(diskConf yobj) (err error) { + diskConf["schema_version"] = 22 + + const field = "blocked_services" + + clients, ok, err := fieldVal[yobj](diskConf, "clients") + if !ok { + return err + } + + persistent, ok, err := fieldVal[yarr](clients, "persistent") + if !ok { + return err + } + + for i, p := range persistent { + var c yobj + c, ok = p.(yobj) + if !ok { + return fmt.Errorf("persistent client at index %d: unexpected type %T", i, p) + } + + var services yarr + services, ok, err = fieldVal[yarr](c, field) + if err != nil { + return fmt.Errorf("persistent client at index %d: %w", i, err) + } else if !ok { + continue + } + + c[field] = yobj{ + "ids": services, + "schedule": yobj{ + "time_zone": "Local", + }, + } + } + + return nil +} diff --git a/internal/confmigrate/v23.go b/internal/confmigrate/v23.go new file mode 100644 index 00000000..6cbcbcf5 --- /dev/null +++ b/internal/confmigrate/v23.go @@ -0,0 +1,59 @@ +package confmigrate + +import ( + "fmt" + "net/netip" + "time" + + "github.com/AdguardTeam/golibs/timeutil" +) + +// migrateTo23 performs the following changes: +// +// # BEFORE: +// 'schema_version': 22 +// 'bind_host': '1.2.3.4' +// 'bind_port': 8080 +// 'web_session_ttl': 720 +// # … +// +// # AFTER: +// 'schema_version': 23 +// 'http': +// 'address': '1.2.3.4:8080' +// 'session_ttl': '720h' +// # … +func migrateTo23(diskConf yobj) (err error) { + diskConf["schema_version"] = 23 + + bindHost, ok, err := fieldVal[string](diskConf, "bind_host") + if !ok { + return err + } + + bindHostAddr, err := netip.ParseAddr(bindHost) + if err != nil { + return fmt.Errorf("invalid bind_host value: %s", bindHost) + } + + bindPort, _, err := fieldVal[int](diskConf, "bind_port") + if err != nil { + return err + } + + sessionTTL, _, err := fieldVal[int](diskConf, "web_session_ttl") + if err != nil { + return err + } + + diskConf["http"] = yobj{ + "address": netip.AddrPortFrom(bindHostAddr, uint16(bindPort)).String(), + "session_ttl": timeutil.Duration{Duration: time.Duration(sessionTTL) * time.Hour}.String(), + } + + delete(diskConf, "bind_host") + delete(diskConf, "bind_port") + delete(diskConf, "web_session_ttl") + + return nil +} diff --git a/internal/confmigrate/v24.go b/internal/confmigrate/v24.go new file mode 100644 index 00000000..e8df5718 --- /dev/null +++ b/internal/confmigrate/v24.go @@ -0,0 +1,50 @@ +package confmigrate + +// migrateTo24 performs the following changes: +// +// # BEFORE: +// 'schema_version': 23 +// 'log_file': "" +// 'log_max_backups': 0 +// 'log_max_size': 100 +// 'log_max_age': 3 +// 'log_compress': false +// 'log_localtime': false +// 'verbose': false +// # … +// +// # AFTER: +// 'schema_version': 24 +// 'log': +// 'file': "" +// 'max_backups': 0 +// 'max_size': 100 +// 'max_age': 3 +// 'compress': false +// 'local_time': false +// 'verbose': false +// # … +func migrateTo24(diskConf yobj) (err error) { + diskConf["schema_version"] = 24 + + logObj := yobj{} + err = coalesceError( + moveVal[string](diskConf, logObj, "log_file", "file"), + moveVal[int](diskConf, logObj, "log_max_backups", "max_backups"), + moveVal[int](diskConf, logObj, "log_max_size", "max_size"), + moveVal[int](diskConf, logObj, "log_max_age", "max_age"), + moveVal[bool](diskConf, logObj, "log_compress", "compress"), + moveVal[bool](diskConf, logObj, "log_localtime", "local_time"), + moveVal[bool](diskConf, logObj, "verbose", "verbose"), + ) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return err + } + + if len(logObj) != 0 { + diskConf["log"] = logObj + } + + return nil +} diff --git a/internal/confmigrate/v25.go b/internal/confmigrate/v25.go new file mode 100644 index 00000000..e574610e --- /dev/null +++ b/internal/confmigrate/v25.go @@ -0,0 +1,38 @@ +package confmigrate + +// migrateTo25 performs the following changes: +// +// # BEFORE: +// 'schema_version': 24 +// 'debug_pprof': true +// # … +// +// # AFTER: +// 'schema_version': 25 +// 'http': +// 'pprof': +// 'enabled': true +// 'port': 6060 +// # … +func migrateTo25(diskConf yobj) (err error) { + diskConf["schema_version"] = 25 + + httpObj, ok, err := fieldVal[yobj](diskConf, "http") + if !ok { + return err + } + + pprofObj := yobj{ + "enabled": false, + "port": 6060, + } + + err = moveVal[bool](diskConf, pprofObj, "debug_pprof", "enabled") + if err != nil { + return err + } + + httpObj["pprof"] = pprofObj + + return nil +} diff --git a/internal/confmigrate/v26.go b/internal/confmigrate/v26.go new file mode 100644 index 00000000..5ead91df --- /dev/null +++ b/internal/confmigrate/v26.go @@ -0,0 +1,111 @@ +package confmigrate + +// migrateTo26 performs the following changes: +// +// # BEFORE: +// 'schema_version': 25 +// 'dns': +// 'filtering_enabled': true +// 'filters_update_interval': 24 +// 'parental_enabled': false +// 'safebrowsing_enabled': false +// 'safebrowsing_cache_size': 1048576 +// 'safesearch_cache_size': 1048576 +// 'parental_cache_size': 1048576 +// 'safe_search': +// 'enabled': false +// 'bing': true +// 'duckduckgo': true +// 'google': true +// 'pixabay': true +// 'yandex': true +// 'youtube': true +// 'rewrites': [] +// 'blocked_services': +// 'schedule': +// 'time_zone': 'Local' +// 'ids': [] +// 'protection_enabled': true, +// 'blocking_mode': 'custom_ip', +// 'blocking_ipv4': '1.2.3.4', +// 'blocking_ipv6': '1:2:3::4', +// 'blocked_response_ttl': 10, +// 'protection_disabled_until': 'null', +// 'parental_block_host': 'p.dns.adguard.com', +// 'safebrowsing_block_host': 's.dns.adguard.com', +// # … +// +// # AFTER: +// 'schema_version': 26 +// 'filtering': +// 'filtering_enabled': true +// 'filters_update_interval': 24 +// 'parental_enabled': false +// 'safebrowsing_enabled': false +// 'safebrowsing_cache_size': 1048576 +// 'safesearch_cache_size': 1048576 +// 'parental_cache_size': 1048576 +// 'safe_search': +// 'enabled': false +// 'bing': true +// 'duckduckgo': true +// 'google': true +// 'pixabay': true +// 'yandex': true +// 'youtube': true +// 'rewrites': [] +// 'blocked_services': +// 'schedule': +// 'time_zone': 'Local' +// 'ids': [] +// 'protection_enabled': true, +// 'blocking_mode': 'custom_ip', +// 'blocking_ipv4': '1.2.3.4', +// 'blocking_ipv6': '1:2:3::4', +// 'blocked_response_ttl': 10, +// 'protection_disabled_until': 'null', +// 'parental_block_host': 'p.dns.adguard.com', +// 'safebrowsing_block_host': 's.dns.adguard.com', +// 'dns' +// # … +// # … +func migrateTo26(diskConf yobj) (err error) { + diskConf["schema_version"] = 26 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + filteringObj := yobj{} + err = coalesceError( + moveSameVal[bool](dns, filteringObj, "filtering_enabled"), + moveSameVal[int](dns, filteringObj, "filters_update_interval"), + moveSameVal[bool](dns, filteringObj, "parental_enabled"), + moveSameVal[bool](dns, filteringObj, "safebrowsing_enabled"), + moveSameVal[int](dns, filteringObj, "safebrowsing_cache_size"), + moveSameVal[int](dns, filteringObj, "safesearch_cache_size"), + moveSameVal[int](dns, filteringObj, "parental_cache_size"), + moveSameVal[yobj](dns, filteringObj, "safe_search"), + moveSameVal[yarr](dns, filteringObj, "rewrites"), + moveSameVal[yobj](dns, filteringObj, "blocked_services"), + moveSameVal[bool](dns, filteringObj, "protection_enabled"), + moveSameVal[string](dns, filteringObj, "blocking_mode"), + moveSameVal[string](dns, filteringObj, "blocking_ipv4"), + moveSameVal[string](dns, filteringObj, "blocking_ipv6"), + moveSameVal[int](dns, filteringObj, "blocked_response_ttl"), + moveSameVal[any](dns, filteringObj, "protection_disabled_until"), + moveSameVal[string](dns, filteringObj, "parental_block_host"), + moveSameVal[string](dns, filteringObj, "safebrowsing_block_host"), + ) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return err + } + + if len(filteringObj) != 0 { + diskConf["filtering"] = filteringObj + } + + return nil +} diff --git a/internal/confmigrate/v27.go b/internal/confmigrate/v27.go new file mode 100644 index 00000000..ddba8671 --- /dev/null +++ b/internal/confmigrate/v27.go @@ -0,0 +1,77 @@ +package confmigrate + +// migrateTo27 performs the following changes: +// +// # BEFORE: +// 'querylog': +// 'ignored': +// - '.' +// - # … +// # … +// 'statistics': +// 'ignored': +// - '.' +// - # … +// # … +// # … +// +// # AFTER: +// 'querylog': +// 'ignored': +// - '|.^' +// - # … +// # … +// 'statistics': +// 'ignored': +// - '|.^' +// - # … +// # … +// # … +func migrateTo27(diskConf yobj) (err error) { + diskConf["schema_version"] = 27 + + keys := []string{"querylog", "statistics"} + for _, k := range keys { + err = replaceDot(diskConf, k) + if err != nil { + return err + } + } + + return nil +} + +// replaceDot replaces rules blocking root domain "." with AdBlock style syntax +// "|.^". +func replaceDot(diskConf yobj, key string) (err error) { + var obj yobj + var ok bool + obj, ok, err = fieldVal[yobj](diskConf, key) + if err != nil { + return err + } else if !ok { + return nil + } + + var ignored yarr + ignored, ok, err = fieldVal[yarr](obj, "ignored") + if err != nil { + return err + } else if !ok { + return nil + } + + for i, hostVal := range ignored { + var host string + host, ok = hostVal.(string) + if !ok { + continue + } + + if host == "." { + ignored[i] = "|.^" + } + } + + return nil +} diff --git a/internal/confmigrate/v3.go b/internal/confmigrate/v3.go new file mode 100644 index 00000000..8220c5df --- /dev/null +++ b/internal/confmigrate/v3.go @@ -0,0 +1,31 @@ +package confmigrate + +// migrateTo3 performs the following changes: +// +// # BEFORE: +// 'schema_version': 2 +// 'dns': +// 'bootstrap_dns': '1.1.1.1' +// # … +// +// # AFTER: +// 'schema_version': 3 +// 'dns': +// 'bootstrap_dns': +// - '1.1.1.1' +// # … +func migrateTo3(diskConf yobj) (err error) { + diskConf["schema_version"] = 3 + + dnsConfig, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + bootstrapDNS, ok, err := fieldVal[any](dnsConfig, "bootstrap_dns") + if ok { + dnsConfig["bootstrap_dns"] = yarr{bootstrapDNS} + } + + return err +} diff --git a/internal/confmigrate/v4.go b/internal/confmigrate/v4.go new file mode 100644 index 00000000..467cc05c --- /dev/null +++ b/internal/confmigrate/v4.go @@ -0,0 +1,30 @@ +package confmigrate + +// migrateTo4 performs the following changes: +// +// # BEFORE: +// 'schema_version': 3 +// 'clients': +// - # … +// # … +// +// # AFTER: +// 'schema_version': 4 +// 'clients': +// - 'use_global_blocked_services': true +// # … +// # … +func migrateTo4(diskConf yobj) (err error) { + diskConf["schema_version"] = 4 + + clients, ok, _ := fieldVal[yarr](diskConf, "clients") + if ok { + for i := range clients { + if c, isYobj := clients[i].(yobj); isYobj { + c["use_global_blocked_services"] = true + } + } + } + + return nil +} diff --git a/internal/confmigrate/v5.go b/internal/confmigrate/v5.go new file mode 100644 index 00000000..7bdd4d00 --- /dev/null +++ b/internal/confmigrate/v5.go @@ -0,0 +1,47 @@ +package confmigrate + +import ( + "fmt" + + "golang.org/x/crypto/bcrypt" +) + +// migrateTo5 performs the following changes: +// +// # BEFORE: +// 'schema_version': 4 +// 'auth_name': … +// 'auth_pass': … +// # … +// +// # AFTER: +// 'schema_version': 5 +// 'users': +// - 'name': … +// 'password': +// # … +func migrateTo5(diskConf yobj) (err error) { + diskConf["schema_version"] = 5 + + user := yobj{} + + if err = moveVal[string](diskConf, user, "auth_name", "name"); err != nil { + return err + } + + pass, ok, err := fieldVal[string](diskConf, "auth_pass") + if !ok { + return err + } + delete(diskConf, "auth_pass") + + hash, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) + if err != nil { + return fmt.Errorf("generating password hash: %w", err) + } + + user["password"] = string(hash) + diskConf["users"] = yarr{user} + + return nil +} diff --git a/internal/confmigrate/v6.go b/internal/confmigrate/v6.go new file mode 100644 index 00000000..aa741ba9 --- /dev/null +++ b/internal/confmigrate/v6.go @@ -0,0 +1,56 @@ +package confmigrate + +import "fmt" + +// migrateTo6 performs the following changes: +// +// # BEFORE: +// 'schema_version': 5 +// 'clients': +// - # … +// 'ip': '127.0.0.1' +// 'mac': 'AA:AA:AA:AA:AA:AA' +// # … +// # … +// +// # AFTER: +// 'schema_version': 6 +// 'clients': +// - # … +// 'ip': '127.0.0.1' +// 'mac': 'AA:AA:AA:AA:AA:AA' +// 'ids': +// - '127.0.0.1' +// - 'AA:AA:AA:AA:AA:AA' +// # … +// # … +func migrateTo6(diskConf yobj) (err error) { + diskConf["schema_version"] = 6 + + clients, ok, err := fieldVal[yarr](diskConf, "clients") + if !ok { + return err + } + + for i, client := range clients { + var c yobj + c, ok = client.(yobj) + if !ok { + return fmt.Errorf("unexpected type of client at index %d: %T", i, client) + } + + ids := yarr{} + for _, id := range []string{"ip", "mac"} { + val, _, valErr := fieldVal[string](c, id) + if valErr != nil { + return fmt.Errorf("client at index %d: %w", i, valErr) + } else if val != "" { + ids = append(ids, val) + } + } + + c["ids"] = ids + } + + return nil +} diff --git a/internal/confmigrate/v7.go b/internal/confmigrate/v7.go new file mode 100644 index 00000000..1db348bd --- /dev/null +++ b/internal/confmigrate/v7.go @@ -0,0 +1,55 @@ +package confmigrate + +// migrateTo7 performs the following changes: +// +// # BEFORE: +// 'schema_version': 6 +// 'dhcp': +// 'enabled': false +// 'interface_name': vboxnet0 +// 'gateway_ip': '192.168.56.1' +// 'subnet_mask': '255.255.255.0' +// 'range_start': '192.168.56.10' +// 'range_end': '192.168.56.240' +// 'lease_duration': 86400 +// 'icmp_timeout_msec': 1000 +// # … +// +// # AFTER: +// 'schema_version': 7 +// 'dhcp': +// 'enabled': false +// 'interface_name': vboxnet0 +// 'dhcpv4': +// 'gateway_ip': '192.168.56.1' +// 'subnet_mask': '255.255.255.0' +// 'range_start': '192.168.56.10' +// 'range_end': '192.168.56.240' +// 'lease_duration': 86400 +// 'icmp_timeout_msec': 1000 +// # … +func migrateTo7(diskConf yobj) (err error) { + diskConf["schema_version"] = 7 + + dhcp, ok, _ := fieldVal[yobj](diskConf, "dhcp") + if !ok { + return nil + } + + dhcpv4 := yobj{} + err = coalesceError( + moveSameVal[string](dhcp, dhcpv4, "gateway_ip"), + moveSameVal[string](dhcp, dhcpv4, "subnet_mask"), + moveSameVal[string](dhcp, dhcpv4, "range_start"), + moveSameVal[string](dhcp, dhcpv4, "range_end"), + moveSameVal[int](dhcp, dhcpv4, "lease_duration"), + moveSameVal[int](dhcp, dhcpv4, "icmp_timeout_msec"), + ) + if err != nil { + return err + } + + dhcp["dhcpv4"] = dhcpv4 + + return nil +} diff --git a/internal/confmigrate/v8.go b/internal/confmigrate/v8.go new file mode 100644 index 00000000..f8075dfb --- /dev/null +++ b/internal/confmigrate/v8.go @@ -0,0 +1,36 @@ +package confmigrate + +// migrateTo8 performs the following changes: +// +// # BEFORE: +// 'schema_version': 7 +// 'dns': +// 'bind_host': '127.0.0.1' +// # … +// # … +// +// # AFTER: +// 'schema_version': 8 +// 'dns': +// 'bind_hosts': +// - '127.0.0.1' +// # … +// # … +func migrateTo8(diskConf yobj) (err error) { + diskConf["schema_version"] = 8 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + bindHost, ok, err := fieldVal[string](dns, "bind_host") + if !ok { + return err + } + + delete(dns, "bind_host") + dns["bind_hosts"] = yarr{bindHost} + + return nil +} diff --git a/internal/confmigrate/v9.go b/internal/confmigrate/v9.go new file mode 100644 index 00000000..b8fc9a72 --- /dev/null +++ b/internal/confmigrate/v9.go @@ -0,0 +1,27 @@ +package confmigrate + +// migrateTo9 performs the following changes: +// +// # BEFORE: +// 'schema_version': 8 +// 'dns': +// 'autohost_tld': 'lan' +// # … +// # … +// +// # AFTER: +// 'schema_version': 9 +// 'dns': +// 'local_domain_name': 'lan' +// # … +// # … +func migrateTo9(diskConf yobj) (err error) { + diskConf["schema_version"] = 9 + + dns, ok, err := fieldVal[yobj](diskConf, "dns") + if !ok { + return err + } + + return moveVal[string](dns, dns, "autohost_tld", "local_domain_name") +} diff --git a/internal/confmigrate/yaml.go b/internal/confmigrate/yaml.go new file mode 100644 index 00000000..778cd1fd --- /dev/null +++ b/internal/confmigrate/yaml.go @@ -0,0 +1,68 @@ +package confmigrate + +import ( + "fmt" +) + +type ( + // yarr is the convenience alias for YAML array. + yarr = []any + + // yobj is the convenience alias for YAML key-value object. + yobj = map[string]any +) + +// fieldVal returns the value of type T for key from obj. Use [any] if the +// field's type doesn't matter. +func fieldVal[T any](obj yobj, key string) (v T, ok bool, err error) { + val, ok := obj[key] + if !ok { + return v, false, nil + } + + if val == nil { + return v, true, nil + } + + v, ok = val.(T) + if !ok { + return v, false, fmt.Errorf("unexpected type of %q: %T", key, val) + } + + return v, true, nil +} + +// moveVal copies the value for srcKey from src into dst for dstKey and deletes +// it from src. +func moveVal[T any](src, dst yobj, srcKey, dstKey string) (err error) { + newVal, ok, err := fieldVal[T](src, srcKey) + if !ok { + return err + } + + dst[dstKey] = newVal + delete(src, srcKey) + + return nil +} + +// moveSameVal moves the value for key from src into dst. +func moveSameVal[T any](src, dst yobj, key string) (err error) { + return moveVal[T](src, dst, key, key) +} + +// coalesceError returns the first non-nil error. It is named after function +// COALESCE in SQL. If all errors are nil, it returns nil. +// +// TODO(e.burkov): Replace with [errors.Join]. +// +// TODO(a.garipov): Think of ways to merge with [aghalg.Coalesce]. +func coalesceError(errors ...error) (res error) { + for _, err := range errors { + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/dhcpd/README.md b/internal/dhcpd/README.md index 5c692e04..8bb51147 100644 --- a/internal/dhcpd/README.md +++ b/internal/dhcpd/README.md @@ -16,17 +16,17 @@ To set up a test environment for DHCP server you will need: ### Configure Virtual Box - 1. Install Virtual Box and run the following command to create a Host-Only + 1. Install Virtual Box and run the following command to create a Host-Only network: ```sh $ VBoxManage hostonlyif create ``` - - You can check its status by `ip a` command. - You can also set up Host-Only network using Virtual Box menu: - + You can check its status by `ip a` command. + + You can also set up Host-Only network using Virtual Box menu: + ``` File -> Host Network Manager... ``` diff --git a/internal/dhcpd/config.go b/internal/dhcpd/config.go index f7919fdf..0721ecd1 100644 --- a/internal/dhcpd/config.go +++ b/internal/dhcpd/config.go @@ -23,9 +23,12 @@ type ServerConfig struct { Enabled bool `yaml:"enabled"` InterfaceName string `yaml:"interface_name"` - // LocalDomainName is the domain name used for DHCP hosts. For example, - // a DHCP client with the hostname "myhost" can be addressed as "myhost.lan" + // LocalDomainName is the domain name used for DHCP hosts. For example, a + // DHCP client with the hostname "myhost" can be addressed as "myhost.lan" // when LocalDomainName is "lan". + // + // TODO(e.burkov): Probably, remove this field. See the TODO on + // [Interface.Enabled]. LocalDomainName string `yaml:"local_domain_name"` Conf4 V4ServerConf `yaml:"dhcpv4"` @@ -58,6 +61,14 @@ type DHCPServer interface { // there is one. FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) + // HostByIP returns a hostname by the IP address of its lease, if there is + // one. + HostByIP(ip netip.Addr) (host string) + + // IPByHost returns an IP address by the hostname of its lease, if there is + // one. + IPByHost(host string) (ip netip.Addr) + // WriteDiskConfig4 - copy disk configuration WriteDiskConfig4(c *V4ServerConf) // WriteDiskConfig6 - copy disk configuration diff --git a/internal/dhcpd/conn_bsd.go b/internal/dhcpd/conn_bsd.go index 8ec630c4..bd7d48dd 100644 --- a/internal/dhcpd/conn_bsd.go +++ b/internal/dhcpd/conn_bsd.go @@ -81,21 +81,6 @@ func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err erro }, nil } -// wrapErrs is a helper to wrap the errors from two independent underlying -// connections. -func (*dhcpConn) wrapErrs(action string, udpConnErr, rawConnErr error) (err error) { - switch { - case udpConnErr != nil && rawConnErr != nil: - return errors.List(fmt.Sprintf("%s both connections", action), udpConnErr, rawConnErr) - case udpConnErr != nil: - return fmt.Errorf("%s udp connection: %w", action, udpConnErr) - case rawConnErr != nil: - return fmt.Errorf("%s raw connection: %w", action, rawConnErr) - default: - return nil - } -} - // WriteTo implements net.PacketConn for *dhcpConn. It selects the underlying // connection to write to based on the type of addr. func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { @@ -117,7 +102,7 @@ func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { Port: dhcpv4.ClientPort, }) - return n, c.wrapErrs("writing to", uerr, rerr) + return n, wrapErrs("writing to", uerr, rerr) case *net.UDPAddr: if addr.IP.Equal(net.IPv4bcast) { // Broadcast the message for the client which supports @@ -157,7 +142,7 @@ func (c *dhcpConn) Close() (err error) { rerr = nil } - return c.wrapErrs("closing", c.udpConn.Close(), rerr) + return wrapErrs("closing", c.udpConn.Close(), rerr) } // LocalAddr implements net.PacketConn for *dhcpConn. @@ -167,12 +152,12 @@ func (c *dhcpConn) LocalAddr() (a net.Addr) { // SetDeadline implements net.PacketConn for *dhcpConn. func (c *dhcpConn) SetDeadline(t time.Time) (err error) { - return c.wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t)) + return wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t)) } // SetReadDeadline implements net.PacketConn for *dhcpConn. func (c *dhcpConn) SetReadDeadline(t time.Time) error { - return c.wrapErrs( + return wrapErrs( "setting reading deadline on", c.udpConn.SetReadDeadline(t), c.rawConn.SetReadDeadline(t), @@ -181,7 +166,7 @@ func (c *dhcpConn) SetReadDeadline(t time.Time) error { // SetWriteDeadline implements net.PacketConn for *dhcpConn. func (c *dhcpConn) SetWriteDeadline(t time.Time) error { - return c.wrapErrs( + return wrapErrs( "setting writing deadline on", c.udpConn.SetWriteDeadline(t), c.rawConn.SetWriteDeadline(t), diff --git a/internal/dhcpd/conn_linux.go b/internal/dhcpd/conn_linux.go index a1c26e51..dc340503 100644 --- a/internal/dhcpd/conn_linux.go +++ b/internal/dhcpd/conn_linux.go @@ -79,21 +79,6 @@ func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err erro }, nil } -// wrapErrs is a helper to wrap the errors from two independent underlying -// connections. -func (*dhcpConn) wrapErrs(action string, udpConnErr, rawConnErr error) (err error) { - switch { - case udpConnErr != nil && rawConnErr != nil: - return errors.List(fmt.Sprintf("%s both connections", action), udpConnErr, rawConnErr) - case udpConnErr != nil: - return fmt.Errorf("%s udp connection: %w", action, udpConnErr) - case rawConnErr != nil: - return fmt.Errorf("%s raw connection: %w", action, rawConnErr) - default: - return nil - } -} - // WriteTo implements net.PacketConn for *dhcpConn. It selects the underlying // connection to write to based on the type of addr. func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { @@ -115,7 +100,7 @@ func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { Port: dhcpv4.ClientPort, }) - return n, c.wrapErrs("writing to", uerr, rerr) + return n, wrapErrs("writing to", uerr, rerr) case *net.UDPAddr: if addr.IP.Equal(net.IPv4bcast) { // Broadcast the message for the client which supports @@ -155,7 +140,7 @@ func (c *dhcpConn) Close() (err error) { rerr = nil } - return c.wrapErrs("closing", c.udpConn.Close(), rerr) + return wrapErrs("closing", c.udpConn.Close(), rerr) } // LocalAddr implements net.PacketConn for *dhcpConn. @@ -165,12 +150,12 @@ func (c *dhcpConn) LocalAddr() (a net.Addr) { // SetDeadline implements net.PacketConn for *dhcpConn. func (c *dhcpConn) SetDeadline(t time.Time) (err error) { - return c.wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t)) + return wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t)) } // SetReadDeadline implements net.PacketConn for *dhcpConn. func (c *dhcpConn) SetReadDeadline(t time.Time) error { - return c.wrapErrs( + return wrapErrs( "setting reading deadline on", c.udpConn.SetReadDeadline(t), c.rawConn.SetReadDeadline(t), @@ -179,7 +164,7 @@ func (c *dhcpConn) SetReadDeadline(t time.Time) error { // SetWriteDeadline implements net.PacketConn for *dhcpConn. func (c *dhcpConn) SetWriteDeadline(t time.Time) error { - return c.wrapErrs( + return wrapErrs( "setting writing deadline on", c.udpConn.SetWriteDeadline(t), c.rawConn.SetWriteDeadline(t), diff --git a/internal/dhcpd/conn_unix.go b/internal/dhcpd/conn_unix.go new file mode 100644 index 00000000..4c34de16 --- /dev/null +++ b/internal/dhcpd/conn_unix.go @@ -0,0 +1,24 @@ +//go:build darwin || freebsd || linux || openbsd + +package dhcpd + +import ( + "fmt" + + "github.com/AdguardTeam/golibs/errors" +) + +// wrapErrs is a helper to wrap the errors from two independent underlying +// connections. +func wrapErrs(action string, udpConnErr, rawConnErr error) (err error) { + switch { + case udpConnErr != nil && rawConnErr != nil: + return fmt.Errorf("%s both connections: %s", action, errors.Join(udpConnErr, rawConnErr)) + case udpConnErr != nil: + return fmt.Errorf("%s udp connection: %w", action, udpConnErr) + case rawConnErr != nil: + return fmt.Errorf("%s raw connection: %w", action, rawConnErr) + default: + return nil + } +} diff --git a/internal/dhcpd/db.go b/internal/dhcpd/db.go index 9ec4716d..8d8c90fa 100644 --- a/internal/dhcpd/db.go +++ b/internal/dhcpd/db.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "os" + "strings" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" @@ -99,8 +100,8 @@ func (s *server) dbStore() (err error) { func writeDB(path string, leases []*Lease) (err error) { defer func() { err = errors.Annotate(err, "writing db: %w") }() - slices.SortFunc(leases, func(a, b *Lease) bool { - return a.Hostname < b.Hostname + slices.SortFunc(leases, func(a, b *Lease) (res int) { + return strings.Compare(a.Hostname, b.Hostname) }) dl := &dataLeases{ diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index 3945c038..e4ee14a9 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -9,6 +9,7 @@ import ( "path/filepath" "time" + "github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/timeutil" "golang.org/x/exp/slices" @@ -31,6 +32,8 @@ const ( // Lease contains the necessary information about a DHCP lease. It's used as is // in the database, so don't change it until it's absolutely necessary, see // [dataVersion]. +// +// TODO(e.burkov): Unexport it and use [dhcpsvc.Lease]. type Lease struct { // Expiry is the expiration time of the lease. Expiry time.Time `json:"expires"` @@ -153,53 +156,37 @@ const ( type Interface interface { Start() (err error) Stop() (err error) + + // Enabled returns true if the DHCP server is running. + // + // TODO(e.burkov): Currently, we need this method to determine whether the + // local domain suffix should be considered while resolving A/AAAA requests. + // This is because other parts of the code aren't aware of the DNS suffixes + // in DHCP clients names and caller is responsible for trimming it. This + // behavior should be changed in the future. Enabled() (ok bool) - Leases(flags GetLeasesFlags) (leases []*Lease) - SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) - FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) + // Leases returns all the leases in the database. + Leases() (leases []*dhcpsvc.Lease) + + // MacByIP returns the MAC address of a client with ip. It returns nil if + // there is no such client, due to an assumption that a DHCP client must + // always have a HardwareAddr. + MACByIP(ip netip.Addr) (mac net.HardwareAddr) + + // HostByIP returns the hostname of the DHCP client with the given IP + // address. The address will be netip.Addr{} if there is no such client, + // due to an assumption that a DHCP client must always have an IP address. + HostByIP(ip netip.Addr) (host string) + + // IPByHost returns the IP address of the DHCP client with the given + // hostname. The address will be netip.Addr{} if there is no such client, + // due to an assumption that a DHCP client must always have an IP address. + IPByHost(host string) (ip netip.Addr) WriteDiskConfig(c *ServerConfig) } -// MockInterface is a mock Interface implementation. -// -// TODO(e.burkov): Move to aghtest when the API stabilized. -type MockInterface struct { - OnStart func() (err error) - OnStop func() (err error) - OnEnabled func() (ok bool) - OnLeases func(flags GetLeasesFlags) (leases []*Lease) - OnSetOnLeaseChanged func(f OnLeaseChangedT) - OnFindMACbyIP func(ip netip.Addr) (mac net.HardwareAddr) - OnWriteDiskConfig func(c *ServerConfig) -} - -var _ Interface = (*MockInterface)(nil) - -// Start implements the Interface for *MockInterface. -func (s *MockInterface) Start() (err error) { return s.OnStart() } - -// Stop implements the Interface for *MockInterface. -func (s *MockInterface) Stop() (err error) { return s.OnStop() } - -// Enabled implements the Interface for *MockInterface. -func (s *MockInterface) Enabled() (ok bool) { return s.OnEnabled() } - -// Leases implements the Interface for *MockInterface. -func (s *MockInterface) Leases(flags GetLeasesFlags) (ls []*Lease) { return s.OnLeases(flags) } - -// 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 netip.Addr) (mac net.HardwareAddr) { - return s.OnFindMACbyIP(ip) -} - -// WriteDiskConfig implements the Interface for *MockInterface. -func (s *MockInterface) WriteDiskConfig(c *ServerConfig) { s.OnWriteDiskConfig(c) } - // server is the DHCP service that handles DHCPv4, DHCPv6, and HTTP API. type server struct { srv4 DHCPServer @@ -269,7 +256,8 @@ func Create(conf *ServerConfig) (s *server, err error) { } // setServers updates DHCPv4 and DHCPv6 servers created from the provided -// configuration conf. +// configuration conf. It returns the status of both the DHCPv4 and the DHCPv6 +// servers, which is always false for corresponding server on any error. func (s *server) setServers(conf *ServerConfig) (v4Enabled, v6Enabled bool, err error) { v4conf := conf.Conf4 v4conf.InterfaceName = s.conf.InterfaceName @@ -279,7 +267,7 @@ func (s *server) setServers(conf *ServerConfig) (v4Enabled, v6Enabled bool, err s.srv4, err = v4Create(&v4conf) if err != nil { if v4conf.Enabled { - return true, false, fmt.Errorf("creating dhcpv4 srv: %w", err) + return false, false, fmt.Errorf("creating dhcpv4 srv: %w", err) } log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err) @@ -288,14 +276,11 @@ func (s *server) setServers(conf *ServerConfig) (v4Enabled, v6Enabled bool, err v6conf := conf.Conf6 v6conf.InterfaceName = s.conf.InterfaceName v6conf.notify = s.onNotify - v6conf.Enabled = s.conf.Enabled - if len(v6conf.RangeStart) == 0 { - v6conf.Enabled = false - } + v6conf.Enabled = s.conf.Enabled && len(v6conf.RangeStart) != 0 s.srv6, err = v6Create(v6conf) if err != nil { - return v4conf.Enabled, v6conf.Enabled, fmt.Errorf("creating dhcpv6 srv: %w", err) + return v4conf.Enabled, false, fmt.Errorf("creating dhcpv6 srv: %w", err) } return v4conf.Enabled, v6conf.Enabled, nil @@ -337,11 +322,6 @@ func (s *server) onNotify(flags uint32) { s.notify(int(flags)) } -// SetOnLeaseChanged - set callback -func (s *server) SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) { - s.onLeaseChanged = append(s.onLeaseChanged, onLeaseChanged) -} - func (s *server) notify(flags int) { for _, f := range s.onLeaseChanged { f(flags) @@ -388,15 +368,26 @@ func (s *server) Stop() (err error) { return nil } -// Leases returns the list of active IPv4 and IPv6 DHCP leases. It's safe for -// concurrent use. -func (s *server) Leases(flags GetLeasesFlags) (leases []*Lease) { - return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...) +// Leases returns the list of active DHCP leases. +func (s *server) Leases() (leases []*dhcpsvc.Lease) { + ls := append(s.srv4.GetLeases(LeasesAll), s.srv6.GetLeases(LeasesAll)...) + leases = make([]*dhcpsvc.Lease, len(ls)) + for i, l := range ls { + leases[i] = &dhcpsvc.Lease{ + Expiry: l.Expiry, + Hostname: l.Hostname, + HWAddr: l.HWAddr, + IP: l.IP, + IsStatic: l.IsStatic, + } + } + + return leases } -// FindMACbyIP returns a MAC address by the IP address of its lease, if there is +// MACByIP 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) { +func (s *server) MACByIP(ip netip.Addr) (mac net.HardwareAddr) { if ip.Is4() { return s.srv4.FindMACbyIP(ip) } @@ -404,6 +395,24 @@ func (s *server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) { return s.srv6.FindMACbyIP(ip) } +// HostByIP implements the [Interface] interface for *server. +// +// TODO(e.burkov): Implement this method for DHCPv6. +func (s *server) HostByIP(ip netip.Addr) (host string) { + if ip.Is4() { + return s.srv4.HostByIP(ip) + } + + return "" +} + +// IPByHost implements the [Interface] interface for *server. +// +// TODO(e.burkov): Implement this method for DHCPv6. +func (s *server) IPByHost(host string) (ip netip.Addr) { + return s.srv4.IPByHost(host) +} + // AddStaticLease - add static v4 lease func (s *server) AddStaticLease(l *Lease) error { return s.srv4.AddStaticLease(l) diff --git a/internal/dhcpd/http_unix.go b/internal/dhcpd/http_unix.go index b07f9543..ecc0c9e3 100644 --- a/internal/dhcpd/http_unix.go +++ b/internal/dhcpd/http_unix.go @@ -14,9 +14,11 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" + "github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" + "golang.org/x/exp/slices" ) type v4ServerConfJSON struct { @@ -75,7 +77,7 @@ type leaseStatic struct { } // leasesToStatic converts list of leases to their JSON form. -func leasesToStatic(leases []*Lease) (static []*leaseStatic) { +func leasesToStatic(leases []*dhcpsvc.Lease) (static []*leaseStatic) { static = make([]*leaseStatic, len(leases)) for i, l := range leases { @@ -113,7 +115,7 @@ type leaseDynamic struct { } // leasesToDynamic converts list of leases to their JSON form. -func leasesToDynamic(leases []*Lease) (dynamic []*leaseDynamic) { +func leasesToDynamic(leases []*dhcpsvc.Lease) (dynamic []*leaseDynamic) { dynamic = make([]*leaseDynamic, len(leases)) for i, l := range leases { @@ -143,10 +145,29 @@ func (s *server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { s.srv4.WriteDiskConfig4(&status.V4) s.srv6.WriteDiskConfig6(&status.V6) - status.Leases = leasesToDynamic(s.Leases(LeasesDynamic)) - status.StaticLeases = leasesToStatic(s.Leases(LeasesStatic)) + leases := s.Leases() + slices.SortFunc(leases, func(a, b *dhcpsvc.Lease) (res int) { + if a.IsStatic == b.IsStatic { + return 0 + } else if a.IsStatic { + return -1 + } else { + return 1 + } + }) - _ = aghhttp.WriteJSONResponse(w, r, status) + dynamicIdx := slices.IndexFunc(leases, func(l *dhcpsvc.Lease) (ok bool) { + return !l.IsStatic + }) + + if dynamicIdx == -1 { + dynamicIdx = len(leases) + } + + status.Leases = leasesToDynamic(leases[dynamicIdx:]) + status.StaticLeases = leasesToStatic(leases[:dynamicIdx]) + + aghhttp.WriteJSONResponseOK(w, r, status) } func (s *server) enableDHCP(ifaceName string) (code int, err error) { @@ -395,7 +416,7 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { } } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // newNetInterfaceJSON creates a JSON object from a [net.Interface] iface. @@ -547,7 +568,7 @@ func (s *server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque setOtherDHCPResult(ifaceName, result) - _ = aghhttp.WriteJSONResponse(w, r, result) + aghhttp.WriteJSONResponseOK(w, r, result) } // setOtherDHCPResult sets the results of the check for another DHCP server in diff --git a/internal/dhcpd/http_windows.go b/internal/dhcpd/http_windows.go index fda72d48..eb82b861 100644 --- a/internal/dhcpd/http_windows.go +++ b/internal/dhcpd/http_windows.go @@ -24,7 +24,7 @@ type jsonError struct { // TODO(a.garipov): Either take the logger from the server after we've // refactored logging or make this not a method of *Server. func (s *server) notImplemented(w http.ResponseWriter, r *http.Request) { - _ = aghhttp.WriteJSONResponseCode(w, r, http.StatusNotImplemented, &jsonError{ + aghhttp.WriteJSONResponse(w, r, http.StatusNotImplemented, &jsonError{ Message: aghos.Unsupported("dhcp").Error(), }) } diff --git a/internal/dhcpd/v46_windows.go b/internal/dhcpd/v46_windows.go index dcdb3caf..dbe22055 100644 --- a/internal/dhcpd/v46_windows.go +++ b/internal/dhcpd/v46_windows.go @@ -24,6 +24,8 @@ func (winServer) WriteDiskConfig4(_ *V4ServerConf) {} func (winServer) WriteDiskConfig6(_ *V6ServerConf) {} func (winServer) Start() (err error) { return nil } func (winServer) Stop() (err error) { return nil } +func (winServer) HostByIP(_ netip.Addr) (host string) { return "" } +func (winServer) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} } 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 34f96210..270a6072 100644 --- a/internal/dhcpd/v4_unix.go +++ b/internal/dhcpd/v4_unix.go @@ -15,7 +15,6 @@ import ( "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" - "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/timeutil" "github.com/go-ping/ping" "github.com/insomniacslk/dhcp/dhcpv4" @@ -46,11 +45,14 @@ type v4Server struct { // leased. leasedOffsets *bitSet - // leaseHosts is the set of all hostnames of all known DHCP clients. - leaseHosts *stringutil.Set - // leases contains all dynamic and static leases. leases []*Lease + + // hostsIndex is the set of all hostnames of all known DHCP clients. + hostsIndex map[string]*Lease + + // ipIndex is an index of leases by their IP addresses. + ipIndex map[netip.Addr]*Lease } func (s *v4Server) enabled() (ok bool) { @@ -114,6 +116,30 @@ func (s *v4Server) validHostnameForClient(cliHostname string, ip netip.Addr) (ho return hostname } +// HostByIP implements the [Interface] interface for *v4Server. +func (s *v4Server) HostByIP(ip netip.Addr) (host string) { + s.leasesLock.Lock() + defer s.leasesLock.Unlock() + + if l, ok := s.ipIndex[ip]; ok { + return l.Hostname + } + + return "" +} + +// IPByHost implements the [Interface] interface for *v4Server. +func (s *v4Server) IPByHost(host string) (ip netip.Addr) { + s.leasesLock.Lock() + defer s.leasesLock.Unlock() + + if l, ok := s.hostsIndex[host]; ok { + return l.IP + } + + return netip.Addr{} +} + // ResetLeases resets leases. func (s *v4Server) ResetLeases(leases []*Lease) (err error) { defer func() { err = errors.Annotate(err, "dhcpv4: %w") }() @@ -123,7 +149,8 @@ func (s *v4Server) ResetLeases(leases []*Lease) (err error) { } s.leasedOffsets = newBitSet() - s.leaseHosts = stringutil.NewSet() + s.hostsIndex = make(map[string]*Lease, len(leases)) + s.ipIndex = make(map[netip.Addr]*Lease, len(leases)) s.leases = nil for _, l := range leases { @@ -199,20 +226,18 @@ func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) { // FindMACbyIP implements the [Interface] for *v4Server. func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) { + if !ip.Is4() { + return nil + } + now := time.Now() s.leasesLock.Lock() defer s.leasesLock.Unlock() - if !ip.Is4() { - return nil - } - - for _, l := range s.leases { - if l.IP == ip { - if l.IsStatic || l.Expiry.After(now) { - return l.HWAddr - } + if l, ok := s.ipIndex[ip]; ok { + if l.IsStatic || l.Expiry.After(now) { + return l.HWAddr } } @@ -249,7 +274,8 @@ func (s *v4Server) rmLeaseByIndex(i int) { s.leasedOffsets.set(offset, false) } - s.leaseHosts.Del(l.Hostname) + delete(s.hostsIndex, l.Hostname) + delete(s.ipIndex, l.IP) log.Debug("dhcpv4: removed lease %s (%s)", l.IP, l.HWAddr) } @@ -303,13 +329,15 @@ func (s *v4Server) addLease(l *Lease) (err error) { return fmt.Errorf("lease %s (%s) out of range, not adding", l.IP, l.HWAddr) } + // TODO(e.burkov): l must have a valid hostname here, investigate. if l.Hostname != "" { - if s.leaseHosts.Has(l.Hostname) { + if _, ok := s.hostsIndex[l.Hostname]; ok { return ErrDupHostname } - s.leaseHosts.Add(l.Hostname) + s.hostsIndex[l.Hostname] = l } + s.ipIndex[l.IP] = l s.leases = append(s.leases, l) s.leasedOffsets.set(offset, true) @@ -574,7 +602,7 @@ func (s *v4Server) commitLease(l *Lease, hostname string) { prev := l.Hostname hostname = s.validHostnameForClient(hostname, l.IP) - if s.leaseHosts.Has(hostname) { + if _, ok := s.hostsIndex[hostname]; ok { log.Info("dhcpv4: hostname %q already exists", hostname) if prev == "" { @@ -590,11 +618,12 @@ func (s *v4Server) commitLease(l *Lease, hostname string) { l.Expiry = time.Now().Add(s.conf.leaseTime) if prev != "" && prev != l.Hostname { - s.leaseHosts.Del(prev) + delete(s.hostsIndex, prev) } if l.Hostname != "" { - s.leaseHosts.Add(l.Hostname) + s.hostsIndex[l.Hostname] = l } + s.ipIndex[l.IP] = l } // allocateLease allocates a new lease for the MAC address. If there are no IP @@ -765,7 +794,7 @@ func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease, return nil, false } - if !s.conf.subnet.Contains(netip.AddrFrom4(*(*[4]byte)(ip4))) { + if !s.conf.subnet.Contains(netip.AddrFrom4([4]byte(ip4))) { // If the DHCP server detects that the client is on the wrong net then // the server SHOULD send a DHCPNAK message to the client. log.Debug("dhcpv4: wrong subnet in init-reboot req msg for %s: %s", mac, reqIP) @@ -1292,7 +1321,8 @@ func (s *v4Server) Stop() (err error) { // Create DHCPv4 server func v4Create(conf *V4ServerConf) (srv *v4Server, err error) { s := &v4Server{ - leaseHosts: stringutil.NewSet(), + hostsIndex: map[string]*Lease{}, + ipIndex: map[netip.Addr]*Lease{}, } err = conf.Validate() diff --git a/internal/dhcpd/v4_unix_test.go b/internal/dhcpd/v4_unix_test.go index 162b5b88..3ad43b74 100644 --- a/internal/dhcpd/v4_unix_test.go +++ b/internal/dhcpd/v4_unix_test.go @@ -791,6 +791,14 @@ func TestV4Server_FindMACbyIP(t *testing.T) { IP: anotherIP, }}, } + s.ipIndex = map[netip.Addr]*Lease{ + staticIP: s.leases[0], + anotherIP: s.leases[1], + } + s.hostsIndex = map[string]*Lease{ + staticName: s.leases[0], + anotherName: s.leases[1], + } testCases := []struct { want net.HardwareAddr @@ -867,7 +875,7 @@ func TestV4Server_handleDecline(t *testing.T) { func TestV4Server_handleRelease(t *testing.T) { const ( - dynamicName = "dymamic-client" + dynamicName = "dynamic-client" anotherName = "another-client" ) diff --git a/internal/dhcpd/v6_unix.go b/internal/dhcpd/v6_unix.go index fa3640f9..f08ea19e 100644 --- a/internal/dhcpd/v6_unix.go +++ b/internal/dhcpd/v6_unix.go @@ -26,15 +26,14 @@ const valueIAID = "ADGH" // value for IANA.ID // // TODO(a.garipov): Think about unifying this and v4Server. type v6Server struct { - srv *server6.Server - leasesLock sync.Mutex - leases []*Lease - ipAddrs [256]byte - sid dhcpv6.DUID - - ra raCtx // RA module - + ra raCtx conf V6ServerConf + sid dhcpv6.DUID + srv *server6.Server + + leases []*Lease + leasesLock sync.Mutex + ipAddrs [256]byte } // WriteDiskConfig4 - write configuration @@ -59,6 +58,34 @@ func ip6InRange(start, ip net.IP) bool { return start[15] <= ip[15] } +// HostByIP implements the [Interface] interface for *v6Server. +func (s *v6Server) HostByIP(ip netip.Addr) (host string) { + s.leasesLock.Lock() + defer s.leasesLock.Unlock() + + for _, l := range s.leases { + if l.IP == ip { + return l.Hostname + } + } + + return "" +} + +// IPByHost implements the [Interface] interface for *v6Server. +func (s *v6Server) IPByHost(host string) (ip netip.Addr) { + s.leasesLock.Lock() + defer s.leasesLock.Unlock() + + for _, l := range s.leases { + if l.Hostname == host { + return l.IP + } + } + + return netip.Addr{} +} + // ResetLeases resets leases. func (s *v6Server) ResetLeases(leases []*Lease) (err error) { defer func() { err = errors.Annotate(err, "dhcpv6: %w") }() diff --git a/internal/dhcpsvc/dhcpsvc.go b/internal/dhcpsvc/dhcpsvc.go index 0c5d1bad..4b3f5c21 100644 --- a/internal/dhcpsvc/dhcpsvc.go +++ b/internal/dhcpsvc/dhcpsvc.go @@ -53,7 +53,7 @@ type Interface interface { // IPByHost returns the IP address of the DHCP client with the given // hostname. The hostname will be an empty string if there is no such // client, due to an assumption that a DHCP client must always have a - // hostname, either set by the client or assigned automatically. + // hostname, either set or generated. IPByHost(host string) (ip netip.Addr) // Leases returns all the DHCP leases. @@ -104,6 +104,9 @@ func (Empty) MACByIP(_ netip.Addr) (mac net.HardwareAddr) { return nil } // IPByHost implements the [Interface] interface for Empty. func (Empty) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} } +// type check +var _ Interface = Empty{} + // Leases implements the [Interface] interface for Empty. func (Empty) Leases() (leases []*Lease) { return nil } diff --git a/internal/dnsforward/access.go b/internal/dnsforward/access.go index c367d05b..2c0d0dc4 100644 --- a/internal/dnsforward/access.go +++ b/internal/dnsforward/access.go @@ -28,6 +28,7 @@ type accessManager struct { allowedClientIDs *stringutil.Set blockedClientIDs *stringutil.Set + // TODO(s.chzhen): Use [aghnet.IgnoreEngine]. blockedHostsEng *urlfilter.DNSEngine // TODO(a.garipov): Create a type for a set of IP networks. @@ -131,7 +132,6 @@ func (a *accessManager) isBlockedClientID(id string) (ok bool) { func (a *accessManager) isBlockedHost(host string, qt rules.RRType) (ok bool) { _, ok = a.blockedHostsEng.MatchRequest(&urlfilter.DNSRequest{ Hostname: host, - ClientIP: "0.0.0.0", DNSType: qt, }) @@ -183,7 +183,7 @@ func (s *Server) accessListJSON() (j accessListJSON) { } func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) { - _ = aghhttp.WriteJSONResponse(w, r, s.accessListJSON()) + aghhttp.WriteJSONResponseOK(w, r, s.accessListJSON()) } // validateAccessSet checks the internal accessListJSON lists. To search for diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index ffd51a78..e5b67d10 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -25,74 +25,19 @@ import ( "golang.org/x/exp/slices" ) -// BlockingMode is an enum of all allowed blocking modes. -type BlockingMode string - -// Allowed blocking modes. -const ( - // BlockingModeCustomIP means respond with a custom IP address. - BlockingModeCustomIP BlockingMode = "custom_ip" - - // BlockingModeDefault is the same as BlockingModeNullIP for - // Adblock-style rules, but responds with the IP address specified in - // the rule when blocked by an `/etc/hosts`-style rule. - BlockingModeDefault BlockingMode = "default" - - // BlockingModeNullIP means respond with a zero IP address: "0.0.0.0" - // for A requests and "::" for AAAA ones. - BlockingModeNullIP BlockingMode = "null_ip" - - // BlockingModeNXDOMAIN means respond with the NXDOMAIN code. - BlockingModeNXDOMAIN BlockingMode = "nxdomain" - - // BlockingModeREFUSED means respond with the REFUSED code. - BlockingModeREFUSED BlockingMode = "refused" -) - -// FilteringConfig represents the DNS filtering configuration of AdGuard Home -// The zero FilteringConfig is empty and ready for use. -type FilteringConfig struct { +// Config represents the DNS filtering configuration of AdGuard Home. The zero +// Config is empty and ready for use. +type Config struct { // Callbacks for other modules // FilterHandler is an optional additional filtering callback. - FilterHandler func(clientAddr net.IP, clientID string, settings *filtering.Settings) `yaml:"-"` + FilterHandler func(cliAddr netip.Addr, clientID string, settings *filtering.Settings) `yaml:"-"` // GetCustomUpstreamByClient is a callback that returns upstreams // configuration based on the client IP address or ClientID. It returns // nil if there are no custom upstreams for the client. GetCustomUpstreamByClient func(id string) (conf *proxy.UpstreamConfig, err error) `yaml:"-"` - // Protection configuration - - // ProtectionEnabled defines whether or not use any of filtering features. - ProtectionEnabled bool `yaml:"protection_enabled"` - - // 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"` - - // ProtectionDisabledUntil is the timestamp until when the protection is - // disabled. - ProtectionDisabledUntil *time.Time `yaml:"protection_disabled_until"` - - // 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 is the maximum number of requests per second from a given IP @@ -118,6 +63,10 @@ type FilteringConfig struct { // resolvers (plain DNS only). BootstrapDNS []string `yaml:"bootstrap_dns"` + // FallbackDNS is the list of fallback DNS servers used when upstream DNS + // servers are not responding. + FallbackDNS []string `yaml:"fallback_dns"` + // AllServers, if true, parallel queries to all configured upstream servers // are enabled. AllServers bool `yaml:"all_servers"` @@ -133,7 +82,7 @@ type FilteringConfig struct { // 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. + // allowed, and [Config.DisallowedClients] are ignored. AllowedClients []string `yaml:"allowed_clients"` // DisallowedClients is the slice of IP addresses, CIDR networks, and @@ -279,7 +228,7 @@ type ServerConfig struct { // Remove that. AddrProcConf *client.DefaultAddrProcConfig - FilteringConfig + Config TLSConfig DNSCryptConfig TLSAllowUnencryptedDoH bool @@ -320,13 +269,6 @@ type ServerConfig struct { UseHTTP3Upstreams bool } -// if any of ServerConfig values are zero, then default values from below are used -var defaultValues = ServerConfig{ - UDPListenAddrs: []*net.UDPAddr{{Port: 53}}, - TCPListenAddrs: []*net.TCPAddr{{Port: 53}}, - FilteringConfig: FilteringConfig{BlockedResponseTTL: 3600}, -} - // createProxyConfig creates and validates configuration for the main proxy. func (s *Server) createProxyConfig() (conf proxy.Config, err error) { srvConf := s.conf @@ -399,10 +341,7 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) { return conf, nil } -const ( - defaultSafeBrowsingBlockHost = "standard-block.dns.adguard.com" - defaultParentalBlockHost = "family-block.dns.adguard.com" -) +const defaultBlockedResponseTTL = 3600 // initDefaultSettings initializes default settings if nothing // is configured @@ -415,20 +354,12 @@ func (s *Server) initDefaultSettings() { s.conf.BootstrapDNS = defaultBootstrap } - if s.conf.ParentalBlockHost == "" { - s.conf.ParentalBlockHost = defaultParentalBlockHost - } - - if s.conf.SafeBrowsingBlockHost == "" { - s.conf.SafeBrowsingBlockHost = defaultSafeBrowsingBlockHost - } - if s.conf.UDPListenAddrs == nil { - s.conf.UDPListenAddrs = defaultValues.UDPListenAddrs + s.conf.UDPListenAddrs = defaultUDPListenAddrs } if s.conf.TCPListenAddrs == nil { - s.conf.TCPListenAddrs = defaultValues.TCPListenAddrs + s.conf.TCPListenAddrs = defaultTCPListenAddrs } if len(s.conf.BlockedHosts) == 0 { @@ -561,9 +492,9 @@ func (s *Server) UpdatedProtectionStatus() (enabled bool, disabledUntil *time.Ti s.serverLock.RLock() defer s.serverLock.RUnlock() - disabledUntil = s.conf.ProtectionDisabledUntil + enabled, disabledUntil = s.dnsFilter.ProtectionStatus() if disabledUntil == nil { - return s.conf.ProtectionEnabled, nil + return enabled, nil } if time.Now().Before(*disabledUntil) { @@ -595,8 +526,7 @@ func (s *Server) enableProtectionAfterPause() { s.serverLock.Lock() defer s.serverLock.Unlock() - s.conf.ProtectionEnabled = true - s.conf.ProtectionDisabledUntil = nil + s.dnsFilter.SetProtectionStatus(true, nil) log.Info("dns: protection is restarted after pause") } diff --git a/internal/dnsforward/dialcontext.go b/internal/dnsforward/dialcontext.go index f917f54c..c2ffe1e7 100644 --- a/internal/dnsforward/dialcontext.go +++ b/internal/dnsforward/dialcontext.go @@ -49,9 +49,8 @@ func (s *Server) DialContext(ctx context.Context, network, addr string) (conn ne continue } - return conn, err + return conn, nil } - // TODO(a.garipov): Use errors.Join in Go 1.20. - return nil, errors.List(fmt.Sprintf("dialing %q", addr), dialErrs...) + return nil, errors.Join(dialErrs...) } diff --git a/internal/dnsforward/dns64_test.go b/internal/dnsforward/dns64_test.go index 85a07fc1..53a18c4e 100644 --- a/internal/dnsforward/dns64_test.go +++ b/internal/dnsforward/dns64_test.go @@ -283,11 +283,13 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) { // right after stop, due to a data race in [proxy.Proxy.Init] method // when setting an OOB size. As a temporary workaround, recreate the // whole server for each test case. - s := createTestServer(t, &filtering.Config{}, ServerConfig{ + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, UseDNS64: true, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, localUps) diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index 894d2ecd..f5fd5e5c 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -15,7 +15,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/client" - "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/AdGuardHome/internal/rdns" @@ -46,20 +45,16 @@ var defaultBootstrap = []string{"9.9.9.10", "149.112.112.10", "2620:fe::10", "26 // Often requested by all kinds of DNS probes var defaultBlockedHosts = []string{"version.bind", "id.server", "hostname.bind"} +var ( + // defaultUDPListenAddrs are the default UDP addresses for the server. + defaultUDPListenAddrs = []*net.UDPAddr{{Port: 53}} + + // defaultTCPListenAddrs are the default TCP addresses for the server. + defaultTCPListenAddrs = []*net.TCPAddr{{Port: 53}} +) + var webRegistered bool -// hostToIPTable is a convenient type alias for tables of host names to an IP -// address. -// -// TODO(e.burkov): Use the [DHCP] interface instead. -type hostToIPTable = map[string]netip.Addr - -// ipToHostTable is a convenient type alias for tables of IP addresses to their -// host names. For example, for use with PTR queries. -// -// TODO(e.burkov): Use the [DHCP] interface instead. -type ipToHostTable = map[netip.Addr]string - // DHCP is an interface for accessing DHCP lease data needed in this package. type DHCP interface { // HostByIP returns the hostname of the DHCP client with the given IP @@ -89,18 +84,34 @@ type DHCP interface { // // The zero Server is empty and ready for use. type Server struct { - dnsProxy *proxy.Proxy // DNS proxy instance - dnsFilter *filtering.DNSFilter // DNS filter instance - dhcpServer dhcpd.Interface // DHCP server instance (optional) - queryLog querylog.QueryLog // Query log instance - stats stats.Interface - access *accessManager + // dnsProxy is the DNS proxy for forwarding client's DNS requests. + dnsProxy *proxy.Proxy + + // dnsFilter is the DNS filter for filtering client's DNS requests and + // responses. + dnsFilter *filtering.DNSFilter + + // dhcpServer is the DHCP server for accessing lease data. + dhcpServer DHCP + + // queryLog is the query log for client's DNS requests, responses and + // filtering results. + queryLog querylog.QueryLog + + // stats is the statistics collector for client's DNS usage data. + stats stats.Interface + + // access drops unallowed clients. + access *accessManager // localDomainSuffix is the suffix used to detect internal hosts. It // must be a valid domain name plus dots on each side. localDomainSuffix string - ipset ipsetCtx + // ipset processes DNS requests using ipset data. + ipset ipsetCtx + + // privateNets is the configured set of IP networks considered private. privateNets netutil.SubnetSet // addrProc, if not nil, is used to process clients' IP addresses with rDNS, @@ -112,7 +123,10 @@ type Server struct { // // TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy. localResolvers *proxy.Proxy - sysResolvers aghnet.SystemResolvers + + // sysResolvers used to fetch system resolvers to use by default for private + // PTR resolving. + sysResolvers aghnet.SystemResolvers // recDetector is a cache for recursive requests. It is used to detect // and prevent recursive requests only for private upstreams. @@ -128,12 +142,6 @@ type Server struct { // anonymizer masks the client's IP addresses if needed. anonymizer *aghnet.IPMut - tableHostToIP hostToIPTable - tableHostToIPLock sync.Mutex - - tableIPToHost ipToHostTable - tableIPToHostLock sync.Mutex - // clientIDCache is a temporary storage for ClientIDs that were extracted // during the BeforeRequestHandler stage. clientIDCache cache.Cache @@ -142,13 +150,16 @@ type Server struct { // We don't Start() it and so no listen port is required. internalProxy *proxy.Proxy + // isRunning is true if the DNS server is running. isRunning bool // protectionUpdateInProgress is used to make sure that only one goroutine // updating the protection configuration after a pause is running at a time. protectionUpdateInProgress atomic.Bool + // conf is the current configuration of the server. conf ServerConfig + // serverLock protects Server. serverLock sync.RWMutex } @@ -164,7 +175,7 @@ type DNSCreateParams struct { DNSFilter *filtering.DNSFilter Stats stats.Interface QueryLog querylog.QueryLog - DHCPServer dhcpd.Interface + DHCPServer DHCP PrivateNets netutil.SubnetSet Anonymizer *aghnet.IPMut LocalDomain string @@ -200,11 +211,12 @@ func NewServer(p DNSCreateParams) (s *Server, err error) { p.Anonymizer = aghnet.NewIPMut(nil) } s = &Server{ - dnsFilter: p.DNSFilter, - stats: p.Stats, - queryLog: p.QueryLog, - privateNets: p.PrivateNets, - localDomainSuffix: localDomainSuffix, + dnsFilter: p.DNSFilter, + stats: p.Stats, + queryLog: p.QueryLog, + privateNets: p.PrivateNets, + // TODO(e.burkov): Use some case-insensitive string comparison. + localDomainSuffix: strings.ToLower(localDomainSuffix), recDetector: newRecursionDetector(recursionTTL, cachedRecurrentReqNum), clientIDCache: cache.New(cache.Config{ EnableLRU: true, @@ -220,11 +232,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) { return nil, fmt.Errorf("initializing system resolvers: %w", err) } - if p.DHCPServer != nil { - s.dhcpServer = p.DHCPServer - s.dhcpServer.SetOnLeaseChanged(s.onDHCPLeaseChanged) - s.onDHCPLeaseChanged(dhcpd.LeaseChangedAdded) - } + s.dhcpServer = p.DHCPServer if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" { // Use plain DNS on MIPS, encryption is too slow @@ -244,7 +252,7 @@ func (s *Server) Close() { s.serverLock.Lock() defer s.serverLock.Unlock() - s.dnsFilter = nil + // TODO(s.chzhen): Remove it. s.stats = nil s.queryLog = nil s.dnsProxy = nil @@ -255,14 +263,15 @@ func (s *Server) Close() { } // WriteDiskConfig - write configuration -func (s *Server) WriteDiskConfig(c *FilteringConfig) { +func (s *Server) WriteDiskConfig(c *Config) { s.serverLock.RLock() defer s.serverLock.RUnlock() - sc := s.conf.FilteringConfig + sc := s.conf.Config *c = sc c.RatelimitWhitelist = stringutil.CloneSlice(sc.RatelimitWhitelist) c.BootstrapDNS = stringutil.CloneSlice(sc.BootstrapDNS) + c.FallbackDNS = stringutil.CloneSlice(sc.FallbackDNS) c.AllowedClients = stringutil.CloneSlice(sc.AllowedClients) c.DisallowedClients = stringutil.CloneSlice(sc.DisallowedClients) c.BlockedHosts = stringutil.CloneSlice(sc.BlockedHosts) @@ -533,9 +542,13 @@ func (s *Server) setupLocalResolvers() (err error) { func (s *Server) Prepare(conf *ServerConfig) (err error) { s.conf = *conf - err = validateBlockingMode(s.conf.BlockingMode, s.conf.BlockingIPv4, s.conf.BlockingIPv6) - if err != nil { - return fmt.Errorf("checking blocking mode: %w", err) + // dnsFilter can be nil during application update. + if s.dnsFilter != nil { + mode, bIPv4, bIPv6 := s.dnsFilter.BlockingMode() + err = validateBlockingMode(mode, bIPv4, bIPv6) + if err != nil { + return fmt.Errorf("checking blocking mode: %w", err) + } } s.initDefaultSettings() @@ -584,6 +597,11 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) { return fmt.Errorf("setting up resolvers: %w", err) } + err = s.setupFallbackDNS() + if err != nil { + return fmt.Errorf("setting up fallback dns servers: %w", err) + } + s.recDetector.clear() s.setupAddrProc() @@ -593,6 +611,28 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) { return nil } +// setupFallbackDNS initializes the fallback DNS servers. +func (s *Server) setupFallbackDNS() (err error) { + fallbacks := s.conf.FallbackDNS + if len(fallbacks) == 0 { + return nil + } + + uc, err := proxy.ParseUpstreamsConfig(fallbacks, &upstream.Options{ + // TODO(s.chzhen): Investigate if other options are needed. + Timeout: s.conf.UpstreamTimeout, + PreferIPv6: s.conf.BootstrapPreferIPv6, + }) + if err != nil { + // Do not wrap the error because it's informative enough as is. + return err + } + + s.dnsProxy.Fallbacks = uc + + return nil +} + // setupAddrProc initializes the address processor. For internal use only. func (s *Server) setupAddrProc() { // TODO(a.garipov): This is a crutch for tests; remove. @@ -617,19 +657,22 @@ func (s *Server) setupAddrProc() { } // validateBlockingMode returns an error if the blocking mode data aren't valid. -func validateBlockingMode(mode BlockingMode, blockingIPv4, blockingIPv6 net.IP) (err error) { +func validateBlockingMode( + mode filtering.BlockingMode, + blockingIPv4, blockingIPv6 netip.Addr, +) (err error) { switch mode { case - BlockingModeDefault, - BlockingModeNXDOMAIN, - BlockingModeREFUSED, - BlockingModeNullIP: + filtering.BlockingModeDefault, + filtering.BlockingModeNXDOMAIN, + filtering.BlockingModeREFUSED, + filtering.BlockingModeNullIP: return nil - case BlockingModeCustomIP: - if blockingIPv4 == nil { - return fmt.Errorf("blocking_ipv4 must be set when blocking_mode is custom_ip") - } else if blockingIPv6 == nil { - return fmt.Errorf("blocking_ipv6 must be set when blocking_mode is custom_ip") + case filtering.BlockingModeCustomIP: + if !blockingIPv4.Is4() { + return fmt.Errorf("blocking_ipv4 must be valid ipv4 on custom_ip blocking_mode") + } else if !blockingIPv6.Is6() { + return fmt.Errorf("blocking_ipv6 must be valid ipv6 on custom_ip blocking_mode") } return nil diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index e1927a65..8417a0df 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -22,7 +22,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" - "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering/hashprefix" "github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch" @@ -94,17 +93,18 @@ func createTestServer( f.SetEnabled(true) + dhcp := &testDHCP{ + OnEnabled: func() (ok bool) { return false }, + OnHostByIP: func(ip netip.Addr) (host string) { return "" }, + OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") }, + } s, err = NewServer(DNSCreateParams{ - DHCPServer: testDHCP, + DHCPServer: dhcp, DNSFilter: f, PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) require.NoError(t, err) - if forwardConf.BlockingMode == "" { - forwardConf.BlockingMode = BlockingModeDefault - } - err = s.Prepare(&forwardConf) require.NoError(t, err) @@ -174,10 +174,12 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte) var keyPem []byte _, certPem, keyPem = createServerTLSConfig(t) - s = createTestServer(t, &filtering.Config{}, ServerConfig{ + s = createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, nil) @@ -231,18 +233,29 @@ func createTestMessageWithType(host string, qtype uint16) *dns.Msg { return req } -func assertGoogleAResponse(t *testing.T, reply *dns.Msg) { - assertResponse(t, reply, net.IP{8, 8, 8, 8}) +// newResp returns the new DNS response with response code set to rcode, req +// used as request, and rrs added. +func newResp(rcode int, req *dns.Msg, ans []dns.RR) (resp *dns.Msg) { + resp = (&dns.Msg{}).SetRcode(req, rcode) + resp.RecursionAvailable = true + resp.Compress = true + resp.Answer = ans + + return resp } -func assertResponse(t *testing.T, reply *dns.Msg, ip net.IP) { +func assertGoogleAResponse(t *testing.T, reply *dns.Msg) { + assertResponse(t, reply, netip.AddrFrom4([4]byte{8, 8, 8, 8})) +} + +func assertResponse(t *testing.T, reply *dns.Msg, ip netip.Addr) { t.Helper() require.Lenf(t, reply.Answer, 1, "dns server returned reply with wrong number of answers - %d", len(reply.Answer)) a, ok := reply.Answer[0].(*dns.A) require.Truef(t, ok, "dns server returned wrong answer type instead of A: %v", reply.Answer[0]) - assert.Truef(t, a.A.Equal(ip), "dns server returned wrong answer instead of %s: %s", ip, a.A) + assert.Equal(t, net.IP(ip.AsSlice()), a.A) } // sendTestMessagesAsync sends messages in parallel to check for race issues. @@ -288,10 +301,12 @@ func sendTestMessages(t *testing.T, conn *dns.Conn) { } func TestServer(t *testing.T) { - s := createTestServer(t, &filtering.Config{}, ServerConfig{ + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, nil) @@ -329,13 +344,12 @@ func TestServer_timeout(t *testing.T) { t.Run("custom", func(t *testing.T) { srvConf := &ServerConfig{ UpstreamTimeout: testTimeout, - FilteringConfig: FilteringConfig{ - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, } - s, err := NewServer(DNSCreateParams{}) + s, err := NewServer(DNSCreateParams{DNSFilter: createTestDNSFilter(t)}) require.NoError(t, err) err = s.Prepare(srvConf) @@ -345,11 +359,10 @@ func TestServer_timeout(t *testing.T) { }) t.Run("default", func(t *testing.T) { - s, err := NewServer(DNSCreateParams{}) + s, err := NewServer(DNSCreateParams{DNSFilter: createTestDNSFilter(t)}) require.NoError(t, err) - s.conf.FilteringConfig.BlockingMode = BlockingModeDefault - s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{ + s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{ Enabled: false, } err = s.Prepare(&s.conf) @@ -360,10 +373,12 @@ func TestServer_timeout(t *testing.T) { } func TestServerWithProtectionDisabled(t *testing.T) { - s := createTestServer(t, &filtering.Config{}, ServerConfig{ + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, nil) @@ -439,9 +454,8 @@ func TestServerRace(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, }, ConfigModified: func() {}, } @@ -462,7 +476,7 @@ func TestSafeSearch(t *testing.T) { OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) { ip4, ip6 := aghtest.HostToIPs(host) - return []net.IP{ip4, ip6}, nil + return []net.IP{ip4.AsSlice(), ip6.AsSlice()}, nil }, } @@ -474,6 +488,8 @@ func TestSafeSearch(t *testing.T) { } filterConf := &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + ProtectionEnabled: true, SafeSearchConf: safeSearchConf, SafeSearchCacheSize: 1000, CacheTime: 30, @@ -490,8 +506,7 @@ func TestSafeSearch(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -503,12 +518,12 @@ func TestSafeSearch(t *testing.T) { addr := s.dnsProxy.Addr(proxy.ProtoUDP).String() client := &dns.Client{} - yandexIP := net.IP{213, 180, 193, 56} + yandexIP := netip.AddrFrom4([4]byte{213, 180, 193, 56}) googleIP, _ := aghtest.HostToIPs("forcesafesearch.google.com") testCases := []struct { host string - want net.IP + want netip.Addr }{{ host: "yandex.com.", want: yandexIP, @@ -548,10 +563,12 @@ func TestSafeSearch(t *testing.T) { } func TestInvalidRequest(t *testing.T) { - s := createTestServer(t, &filtering.Config{}, ServerConfig{ + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -579,15 +596,16 @@ func TestBlockedRequest(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + }, forwardConf, nil) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -608,14 +626,15 @@ func TestServerCustomClientUpstream(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, forwardConf, nil) s.conf.GetCustomUpstreamByClient = func(_ string) (conf *proxy.UpstreamConfig, err error) { ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) { return aghalg.Coalesce( @@ -658,10 +677,12 @@ var testIPv4 = map[string][]net.IP{ } func TestBlockCNAMEProtectionEnabled(t *testing.T) { - s := createTestServer(t, &filtering.Config{}, ServerConfig{ + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -671,7 +692,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { CName: testCNAMEs, IPv4: testIPv4, } - s.conf.ProtectionEnabled = false + s.dnsProxy.UpstreamConfig = &proxy.UpstreamConfig{ Upstreams: []upstream.Upstream{testUpstm}, } @@ -693,15 +714,16 @@ func TestBlockCNAME(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + }, forwardConf, nil) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ &aghtest.Upstream{ CName: testCNAMEs, @@ -763,9 +785,8 @@ func TestClientRulesForCNAMEMatching(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - FilterHandler: func(_ net.IP, _ string, settings *filtering.Settings) { + Config: Config{ + FilterHandler: func(_ netip.Addr, _ string, settings *filtering.Settings) { settings.FilteringEnabled = false }, EDNSClientSubnet: &EDNSClientSubnet{ @@ -773,7 +794,9 @@ func TestClientRulesForCNAMEMatching(t *testing.T) { }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, forwardConf, nil) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ &aghtest.Upstream{ CName: testCNAMEs, @@ -809,15 +832,16 @@ func TestNullBlockedRequest(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeNullIP, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeNullIP, + }, forwardConf, nil) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -849,11 +873,21 @@ func TestBlockedCustomIP(t *testing.T) { Data: []byte(rules), }} - f, err := filtering.New(&filtering.Config{}, filters) + f, err := filtering.New(&filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeCustomIP, + BlockingIPv4: netip.Addr{}, + BlockingIPv6: netip.Addr{}, + }, filters) require.NoError(t, err) + dhcp := &testDHCP{ + OnEnabled: func() (ok bool) { return false }, + OnHostByIP: func(_ netip.Addr) (host string) { panic("not implemented") }, + OnIPByHost: func(_ string) (ip netip.Addr) { panic("not implemented") }, + } s, err := NewServer(DNSCreateParams{ - DHCPServer: testDHCP, + DHCPServer: dhcp, DNSFilter: f, PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) @@ -862,11 +896,8 @@ func TestBlockedCustomIP(t *testing.T) { conf := &ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeCustomIP, - BlockingIPv4: nil, - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -877,8 +908,10 @@ func TestBlockedCustomIP(t *testing.T) { err = s.Prepare(conf) assert.Error(t, err) - conf.BlockingIPv4 = net.IP{0, 0, 0, 1} - conf.BlockingIPv6 = net.ParseIP("::1") + s.dnsFilter.SetBlockingMode( + filtering.BlockingModeCustomIP, + netip.AddrFrom4([4]byte{0, 0, 0, 1}), + netip.MustParseAddr("::1")) err = s.Prepare(conf) require.NoError(t, err) @@ -915,16 +948,17 @@ func TestBlockedByHosts(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + }, forwardConf, nil) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -955,15 +989,16 @@ func TestBlockedBySafeBrowsing(t *testing.T) { ans4, _ := aghtest.HostToIPs(hostname) filterConf := &filtering.Config{ - SafeBrowsingEnabled: true, - SafeBrowsingChecker: sbChecker, + BlockingMode: filtering.BlockingModeDefault, + ProtectionEnabled: true, + SafeBrowsingEnabled: true, + SafeBrowsingChecker: sbChecker, + SafeBrowsingBlockHost: ans4.String(), } forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - SafeBrowsingBlockHost: ans4.String(), - ProtectionEnabled: true, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -980,13 +1015,12 @@ func TestBlockedBySafeBrowsing(t *testing.T) { require.NoErrorf(t, err, "couldn't talk to server %s: %s", addr, err) require.Lenf(t, reply.Answer, 1, "dns server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) - a, ok := reply.Answer[0].(*dns.A) - require.Truef(t, ok, "dns server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) - assert.Equal(t, ans4, a.A, "dns server %s returned wrong answer: %v", addr, a.A) + assertResponse(t, reply, ans4) } func TestRewrite(t *testing.T) { c := &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, Rewrites: []*filtering.LegacyRewrite{{ Domain: "test.com", Answer: "1.2.3.4", @@ -1006,8 +1040,13 @@ func TestRewrite(t *testing.T) { f.SetEnabled(true) + dhcp := &testDHCP{ + OnEnabled: func() (ok bool) { return false }, + OnHostByIP: func(ip netip.Addr) (host string) { panic("not implemented") }, + OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") }, + } s, err := NewServer(DNSCreateParams{ - DHCPServer: testDHCP, + DHCPServer: dhcp, DNSFilter: f, PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) @@ -1016,10 +1055,8 @@ func TestRewrite(t *testing.T) { assert.NoError(t, s.Prepare(&ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, - UpstreamDNS: []string{"8.8.8.8:53"}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53"}, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -1102,31 +1139,42 @@ func publicKey(priv any) any { } } -var testDHCP = &dhcpd.MockInterface{ - OnStart: func() (err error) { panic("not implemented") }, - OnStop: func() (err error) { panic("not implemented") }, - OnEnabled: func() (ok bool) { return true }, - OnLeases: func(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) { - return []*dhcpd.Lease{{ - IP: netip.MustParseAddr("192.168.12.34"), - HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - Hostname: "myhost", - }} - }, - OnSetOnLeaseChanged: func(olct dhcpd.OnLeaseChangedT) {}, - OnFindMACbyIP: func(ip netip.Addr) (mac net.HardwareAddr) { panic("not implemented") }, - OnWriteDiskConfig: func(c *dhcpd.ServerConfig) { panic("not implemented") }, +// testDHCP is a mock implementation of the [DHCP] interface. +type testDHCP struct { + OnHostByIP func(ip netip.Addr) (host string) + OnIPByHost func(host string) (ip netip.Addr) + OnEnabled func() (ok bool) } +// type check +var _ DHCP = (*testDHCP)(nil) + +// HostByIP implements the [DHCP] interface for *testDHCP. +func (d *testDHCP) HostByIP(ip netip.Addr) (host string) { return d.OnHostByIP(ip) } + +// IPByHost implements the [DHCP] interface for *testDHCP. +func (d *testDHCP) IPByHost(host string) (ip netip.Addr) { return d.OnIPByHost(host) } + +// IsClientHost implements the [DHCP] interface for *testDHCP. +func (d *testDHCP) Enabled() (ok bool) { return d.OnEnabled() } + func TestPTRResponseFromDHCPLeases(t *testing.T) { const localDomain = "lan" - flt, err := filtering.New(&filtering.Config{}, nil) + flt, err := filtering.New(&filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, nil) require.NoError(t, err) s, err := NewServer(DNSCreateParams{ - DNSFilter: flt, - DHCPServer: testDHCP, + DNSFilter: flt, + DHCPServer: &testDHCP{ + OnEnabled: func() (ok bool) { return true }, + OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") }, + OnHostByIP: func(ip netip.Addr) (host string) { + return "myhost" + }, + }, PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), LocalDomain: localDomain, }) @@ -1135,9 +1183,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { s.conf.UDPListenAddrs = []*net.UDPAddr{{}} s.conf.TCPListenAddrs = []*net.TCPAddr{{}} 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} + s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} err = s.Prepare(&s.conf) require.NoError(t, err) @@ -1175,8 +1221,14 @@ func TestPTRResponseFromHosts(t *testing.T) { `)}, } + dhcp := &testDHCP{ + OnEnabled: func() (ok bool) { return false }, + OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") }, + OnHostByIP: func(ip netip.Addr) (host string) { return "" }, + } + var eventsCalledCounter uint32 - hc, err := aghnet.NewHostsContainer(0, testFS, &aghtest.FSWatcher{ + hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{ OnEvents: func() (e <-chan struct{}) { assert.Equal(t, uint32(1), atomic.AddUint32(&eventsCalledCounter, 1)) @@ -1195,7 +1247,8 @@ func TestPTRResponseFromHosts(t *testing.T) { }) flt, err := filtering.New(&filtering.Config{ - EtcHosts: hc, + BlockingMode: filtering.BlockingModeDefault, + EtcHosts: hc, }, nil) require.NoError(t, err) @@ -1203,7 +1256,7 @@ func TestPTRResponseFromHosts(t *testing.T) { var s *Server s, err = NewServer(DNSCreateParams{ - DHCPServer: testDHCP, + DHCPServer: dhcp, DNSFilter: flt, PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) @@ -1212,8 +1265,7 @@ func TestPTRResponseFromHosts(t *testing.T) { s.conf.UDPListenAddrs = []*net.UDPAddr{{}} 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} + s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} err = s.Prepare(&s.conf) require.NoError(t, err) diff --git a/internal/dnsforward/dnsrewrite.go b/internal/dnsforward/dnsrewrite.go index 47eab12b..8b9a0fb1 100644 --- a/internal/dnsforward/dnsrewrite.go +++ b/internal/dnsforward/dnsrewrite.go @@ -2,7 +2,7 @@ package dnsforward import ( "fmt" - "net" + "net/netip" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" @@ -44,13 +44,13 @@ func (s *Server) ansFromDNSRewriteIP( rr rules.RRType, req *dns.Msg, ) (ans dns.RR, err error) { - ip, ok := v.(net.IP) + ip, ok := v.(netip.Addr) if !ok { - return nil, fmt.Errorf("value for rr type %s has type %T, not net.IP", dns.Type(rr), v) + return nil, fmt.Errorf("value for rr type %s has type %T, not netip.Addr", dns.Type(rr), v) } if rr == dns.TypeA { - return s.genAnswerA(req, ip.To4()), nil + return s.genAnswerA(req, ip), nil } return s.genAnswerAAAA(req, ip), nil @@ -160,13 +160,13 @@ func (s *Server) filterDNSRewrite( return errors.Error("no dns rewrite rule responses") } - rr := req.Question[0].Qtype - values := dnsrr.Response[rr] + qtype := req.Question[0].Qtype + values := dnsrr.Response[qtype] for i, v := range values { var ans dns.RR - ans, err = s.filterDNSRewriteResponse(req, rr, v) + ans, err = s.filterDNSRewriteResponse(req, qtype, v) if err != nil { - return fmt.Errorf("dns rewrite response for %d[%d]: %w", rr, i, err) + return fmt.Errorf("dns rewrite response for %s[%d]: %w", dns.Type(qtype), i, err) } resp.Answer = append(resp.Answer, ans) diff --git a/internal/dnsforward/dnsrewrite_test.go b/internal/dnsforward/dnsrewrite_test.go index 422759a3..79aecdef 100644 --- a/internal/dnsforward/dnsrewrite_test.go +++ b/internal/dnsforward/dnsrewrite_test.go @@ -6,6 +6,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -15,8 +16,7 @@ import ( func TestServer_FilterDNSRewrite(t *testing.T) { // Helper data. const domain = "example.com" - ip4 := net.IP{127, 0, 0, 1} - ip6 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + ip4, ip6 := netutil.IPv4Localhost(), netutil.IPv6Localhost() mxVal := &rules.DNSMX{ Exchange: "mail.example.com", Preference: 32, @@ -34,7 +34,14 @@ func TestServer_FilterDNSRewrite(t *testing.T) { } // Helper functions and entities. - srv := &Server{} + srv := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ + Config: Config{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, + }, nil) + makeQ := func(qtype rules.RRType) (req *dns.Msg) { return &dns.Msg{ Question: []dns.Question{{ @@ -89,7 +96,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) { assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) require.Len(t, d.Res.Answer, 1) - assert.Equal(t, ip4, d.Res.Answer[0].(*dns.A).A) + assert.Equal(t, net.IP(ip4.AsSlice()), d.Res.Answer[0].(*dns.A).A) }) t.Run("noerror_aaaa", func(t *testing.T) { @@ -103,7 +110,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) { assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) require.Len(t, d.Res.Answer, 1) - assert.Equal(t, ip6, d.Res.Answer[0].(*dns.AAAA).AAAA) + assert.Equal(t, net.IP(ip6.AsSlice()), d.Res.Answer[0].(*dns.AAAA).AAAA) }) t.Run("noerror_ptr", func(t *testing.T) { diff --git a/internal/dnsforward/filter.go b/internal/dnsforward/filter.go index 4dee0c07..6f551e59 100644 --- a/internal/dnsforward/filter.go +++ b/internal/dnsforward/filter.go @@ -3,6 +3,7 @@ package dnsforward import ( "encoding/binary" "fmt" + "net" "strings" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" @@ -11,6 +12,7 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" "github.com/miekg/dns" + "golang.org/x/exp/slices" ) // beforeRequestHandler is the handler that is called before any other @@ -57,8 +59,8 @@ func (s *Server) clientRequestFilteringSettings(dctx *dnsContext) (setts *filter setts = s.dnsFilter.Settings() setts.ProtectionEnabled = dctx.protectionEnabled if s.conf.FilterHandler != nil { - ip, _ := netutil.IPAndPortFromAddr(dctx.proxyCtx.Addr) - s.conf.FilterHandler(ip, dctx.clientID, setts) + addrPort := netutil.NetAddrToAddrPort(dctx.proxyCtx.Addr) + s.conf.FilterHandler(addrPort.Addr(), dctx.clientID, setts) } return setts @@ -123,7 +125,7 @@ func (s *Server) filterRewritten( for _, ip := range res.IPList { switch qt { case dns.TypeA: - a := s.genAnswerA(req, ip.To4()) + a := s.genAnswerA(req, ip) a.Hdr.Name = dns.Fqdn(name) resp.Answer = append(resp.Answer, a) case dns.TypeAAAA: @@ -145,10 +147,6 @@ func (s *Server) checkHostRules(host string, rrtype uint16, setts *filtering.Set s.serverLock.RLock() defer s.serverLock.RUnlock() - if s.dnsFilter == nil { - return nil, nil - } - var res filtering.Result res, err = s.dnsFilter.CheckHostRules(host, rrtype, setts) if err != nil { @@ -176,19 +174,26 @@ func (s *Server) filterDNSResponse( case *dns.CNAME: host = strings.TrimSuffix(a.Target, ".") rrtype = dns.TypeCNAME + + res, err = s.checkHostRules(host, rrtype, setts) case *dns.A: host = a.A.String() rrtype = dns.TypeA + + res, err = s.checkHostRules(host, rrtype, setts) case *dns.AAAA: host = a.AAAA.String() rrtype = dns.TypeAAAA + + res, err = s.checkHostRules(host, rrtype, setts) + case *dns.HTTPS: + res, err = s.filterHTTPSRecords(a, setts) default: continue } - log.Debug("dnsforward: checking %s %s for %s", dns.Type(rrtype), host, a.Header().Name) + log.Debug("dnsforward: checked %s %s for %s", dns.Type(rrtype), host, a.Header().Name) - res, err = s.checkHostRules(host, rrtype, setts) if err != nil { return nil, err } else if res == nil { @@ -203,3 +208,67 @@ func (s *Server) filterDNSResponse( return nil, nil } + +// removeIPv6Hints deletes IPv6 hints from RR values. +func removeIPv6Hints(rr *dns.HTTPS) { + rr.Value = slices.DeleteFunc(rr.Value, func(kv dns.SVCBKeyValue) (del bool) { + _, ok := kv.(*dns.SVCBIPv6Hint) + + return ok + }) +} + +// filterHTTPSRecords filters HTTPS answers information through all rule list +// filters of the server filters. Removes IPv6 hints if IPv6 resolving is +// disabled. +func (s *Server) filterHTTPSRecords(rr *dns.HTTPS, setts *filtering.Settings) (r *filtering.Result, err error) { + if s.conf.AAAADisabled { + removeIPv6Hints(rr) + } + + for _, kv := range rr.Value { + var ips []net.IP + switch hint := kv.(type) { + case *dns.SVCBIPv4Hint: + ips = hint.Hint + case *dns.SVCBIPv6Hint: + ips = hint.Hint + default: + // Go on. + } + + if len(ips) == 0 { + continue + } + + r, err = s.filterSVCBHint(ips, setts) + if err != nil { + return nil, fmt.Errorf("filtering svcb hints: %w", err) + } + + if r != nil { + return r, nil + } + } + + return nil, nil +} + +// filterSVCBHint filters SVCB hint information. +func (s *Server) filterSVCBHint( + hint []net.IP, + setts *filtering.Settings, +) (res *filtering.Result, err error) { + for _, h := range hint { + res, err = s.checkHostRules(h.String(), dns.TypeHTTPS, setts) + if err != nil { + return nil, fmt.Errorf("checking rules for %s: %w", h, err) + } + + if res != nil && res.IsFiltered { + return res, nil + } + } + + return nil, nil +} diff --git a/internal/dnsforward/filter_test.go b/internal/dnsforward/filter_test.go index 1e3c4822..88a316e8 100644 --- a/internal/dnsforward/filter_test.go +++ b/internal/dnsforward/filter_test.go @@ -2,6 +2,7 @@ package dnsforward import ( "net" + "net/netip" "testing" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" @@ -14,7 +15,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { +func TestHandleDNSRequest_handleDNSRequest(t *testing.T) { rules := ` ||blocked.domain^ @@||allowed.domain^ @@ -23,14 +24,13 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { ||::1^$dnstype=~AAAA 0.0.0.0 duplicate.domain 0.0.0.0 duplicate.domain +0.0.0.0 blocked.by.hostrule ` forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -40,12 +40,19 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { ID: 0, Data: []byte(rules), }} - f, err := filtering.New(&filtering.Config{}, filters) + f, err := filtering.New(&filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + }, filters) require.NoError(t, err) f.SetEnabled(true) s, err := NewServer(DNSCreateParams{ - DHCPServer: testDHCP, + DHCPServer: &testDHCP{ + OnEnabled: func() (ok bool) { return false }, + OnHostByIP: func(ip netip.Addr) (host string) { panic("not implemented") }, + OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") }, + }, DNSFilter: f, PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) @@ -73,12 +80,19 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { startDeferStop(t, s) testCases := []struct { - req *dns.Msg - name string - wantAns []dns.RR + req *dns.Msg + name string + wantRCode int + wantAns []dns.RR }{{ - req: createTestMessage("cname.exception."), - name: "cname_exception", + req: createTestMessage(aghtest.ReqFQDN), + name: "pass", + wantRCode: dns.RcodeNameError, + wantAns: nil, + }, { + req: createTestMessage("cname.exception."), + name: "cname_exception", + wantRCode: dns.RcodeSuccess, wantAns: []dns.RR{&dns.CNAME{ Hdr: dns.RR_Header{ Name: "cname.exception.", @@ -87,8 +101,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { Target: "cname.specific.", }}, }, { - req: createTestMessage("should.block."), - name: "blocked_by_cname", + req: createTestMessage("should.block."), + name: "blocked_by_cname", + wantRCode: dns.RcodeSuccess, wantAns: []dns.RR{&dns.A{ Hdr: dns.RR_Header{ Name: "should.block.", @@ -98,8 +113,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { A: netutil.IPv4Zero(), }}, }, { - req: createTestMessage("a.exception."), - name: "a_exception", + req: createTestMessage("a.exception."), + name: "a_exception", + wantRCode: dns.RcodeSuccess, wantAns: []dns.RR{&dns.A{ Hdr: dns.RR_Header{ Name: "a.exception.", @@ -108,8 +124,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { A: net.IP{0, 0, 0, 1}, }}, }, { - req: createTestMessageWithType("aaaa.exception.", dns.TypeAAAA), - name: "aaaa_exception", + req: createTestMessageWithType("aaaa.exception.", dns.TypeAAAA), + name: "aaaa_exception", + wantRCode: dns.RcodeSuccess, wantAns: []dns.RR{&dns.AAAA{ Hdr: dns.RR_Header{ Name: "aaaa.exception.", @@ -118,8 +135,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { AAAA: net.ParseIP("::1"), }}, }, { - req: createTestMessage("allowed.first."), - name: "allowed_first", + req: createTestMessage("allowed.first."), + name: "allowed_first", + wantRCode: dns.RcodeSuccess, wantAns: []dns.RR{&dns.A{ Hdr: dns.RR_Header{ Name: "allowed.first.", @@ -129,8 +147,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { A: netutil.IPv4Zero(), }}, }, { - req: createTestMessage("blocked.first."), - name: "blocked_first", + req: createTestMessage("blocked.first."), + name: "blocked_first", + wantRCode: dns.RcodeSuccess, wantAns: []dns.RR{&dns.A{ Hdr: dns.RR_Header{ Name: "blocked.first.", @@ -140,8 +159,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { A: netutil.IPv4Zero(), }}, }, { - req: createTestMessage("duplicate.domain."), - name: "duplicate_domain", + req: createTestMessage("duplicate.domain."), + name: "duplicate_domain", + wantRCode: dns.RcodeSuccess, wantAns: []dns.RR{&dns.A{ Hdr: dns.RR_Header{ Name: "duplicate.domain.", @@ -150,6 +170,16 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { }, A: netutil.IPv4Zero(), }}, + }, { + req: createTestMessageWithType("blocked.domain.", dns.TypeHTTPS), + name: "blocked_https_req", + wantRCode: dns.RcodeSuccess, + wantAns: nil, + }, { + req: createTestMessageWithType("blocked.by.hostrule.", dns.TypeHTTPS), + name: "blocked_host_rule_https_req", + wantRCode: dns.RcodeSuccess, + wantAns: nil, }} for _, tc := range testCases { @@ -164,7 +194,175 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { require.NoError(t, err) require.NotNil(t, dctx.Res) + assert.Equal(t, tc.wantRCode, dctx.Res.Rcode) assert.Equal(t, tc.wantAns, dctx.Res.Answer) }) } } + +func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { + const ( + passedIPv4Str = "1.1.1.1" + blockedIPv4Str = "1.2.3.4" + blockedIPv6Str = "1234::cdef" + blockRules = blockedIPv4Str + "\n" + blockedIPv6Str + "\n" + ) + + var ( + passedIPv4 net.IP = netip.MustParseAddr(passedIPv4Str).AsSlice() + blockedIPv4 net.IP = netip.MustParseAddr(blockedIPv4Str).AsSlice() + blockedIPv6 net.IP = netip.MustParseAddr(blockedIPv6Str).AsSlice() + ) + + filters := []filtering.Filter{{ + ID: 0, Data: []byte(blockRules), + }} + + f, err := filtering.New(&filtering.Config{}, filters) + require.NoError(t, err) + + f.SetEnabled(true) + + s, err := NewServer(DNSCreateParams{ + DHCPServer: &testDHCP{}, + DNSFilter: f, + PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), + }) + require.NoError(t, err) + + testCases := []struct { + req *dns.Msg + name string + wantRule string + respAns []dns.RR + }{{ + name: "pass", + req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeA), + wantRule: "", + respAns: []dns.RR{&dns.A{ + Hdr: dns.RR_Header{ + Name: aghtest.ReqFQDN, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: passedIPv4, + }}, + }, { + name: "ipv4", + req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeA), + wantRule: blockedIPv4Str, + respAns: []dns.RR{&dns.A{ + Hdr: dns.RR_Header{ + Name: aghtest.ReqFQDN, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: blockedIPv4, + }}, + }, { + name: "ipv6", + req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeAAAA), + wantRule: blockedIPv6Str, + respAns: []dns.RR{&dns.AAAA{ + Hdr: dns.RR_Header{ + Name: aghtest.ReqFQDN, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + }, + AAAA: blockedIPv6, + }}, + }, { + name: "ipv4hint", + req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS), + wantRule: blockedIPv4Str, + respAns: newSVCBHintsAnswer( + aghtest.ReqFQDN, + []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{blockedIPv4}}, + &dns.SVCBIPv6Hint{Hint: []net.IP{}}, + }, + ), + }, { + name: "ipv6hint", + req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS), + wantRule: blockedIPv6Str, + respAns: newSVCBHintsAnswer( + aghtest.ReqFQDN, + []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{}}, + &dns.SVCBIPv6Hint{Hint: []net.IP{blockedIPv6}}, + }, + ), + }, { + name: "ipv4_ipv6_hints", + req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS), + wantRule: blockedIPv4Str, + respAns: newSVCBHintsAnswer( + aghtest.ReqFQDN, + []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{blockedIPv4}}, + &dns.SVCBIPv6Hint{Hint: []net.IP{blockedIPv6}}, + }, + ), + }, { + name: "pass_hints", + req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS), + wantRule: "", + respAns: newSVCBHintsAnswer( + aghtest.ReqFQDN, + []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{passedIPv4}}, + &dns.SVCBIPv6Hint{Hint: []net.IP{}}, + }, + ), + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resp := newResp(dns.RcodeSuccess, tc.req, tc.respAns) + + pctx := &proxy.DNSContext{ + Proto: proxy.ProtoUDP, + Req: tc.req, + Res: resp, + Addr: &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 1}, + } + + res, rErr := s.filterDNSResponse(pctx, &filtering.Settings{ + ProtectionEnabled: true, + FilteringEnabled: true, + }) + require.NoError(t, rErr) + + if tc.wantRule == "" { + assert.Nil(t, res) + + return + } + + want := &filtering.Result{ + IsFiltered: true, + Reason: filtering.FilteredBlockList, + Rules: []*filtering.ResultRule{{ + Text: tc.wantRule, + }}, + } + assert.Equal(t, want, res) + }) + } +} + +// newSVCBHintsAnswer returns a test HTTPS answer RRs with SVCB hints. +func newSVCBHintsAnswer(target string, hints []dns.SVCBKeyValue) (rrs []dns.RR) { + return []dns.RR{&dns.HTTPS{ + SVCB: dns.SVCB{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeHTTPS, + Class: dns.ClassINET, + }, + Target: target, + Value: hints, + }, + }} +} diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index 761cbeb4..3827e101 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -4,13 +4,13 @@ import ( "encoding/json" "fmt" "io" - "net" "net/http" "net/netip" "strings" "time" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" + "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/errors" @@ -37,6 +37,10 @@ type jsonDNSConfig struct { // upstream DoH/DoT resolvers. Bootstraps *[]string `json:"bootstrap_dns"` + // Fallbacks is the list of fallback DNS servers used when upstream DNS + // servers are not responding. + Fallbacks *[]string `json:"fallback_dns"` + // ProtectionEnabled defines if protection is enabled. ProtectionEnabled *bool `json:"protection_enabled"` @@ -44,7 +48,7 @@ type jsonDNSConfig struct { RateLimit *uint32 `json:"ratelimit"` // BlockingMode defines the way blocked responses are constructed. - BlockingMode *BlockingMode `json:"blocking_mode"` + BlockingMode *filtering.BlockingMode `json:"blocking_mode"` // EDNSCSEnabled defines if EDNS Client Subnet is enabled. EDNSCSEnabled *bool `json:"edns_cs_enabled"` @@ -83,10 +87,10 @@ type jsonDNSConfig struct { LocalPTRUpstreams *[]string `json:"local_ptr_upstreams"` // BlockingIPv4 is custom IPv4 address for blocked A requests. - BlockingIPv4 net.IP `json:"blocking_ipv4"` + BlockingIPv4 netip.Addr `json:"blocking_ipv4"` // BlockingIPv6 is custom IPv6 address for blocked AAAA requests. - BlockingIPv6 net.IP `json:"blocking_ipv6"` + BlockingIPv6 netip.Addr `json:"blocking_ipv6"` // DisabledUntil is a timestamp until when the protection is disabled. DisabledUntil *time.Time `json:"protection_disabled_until"` @@ -109,9 +113,8 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) { upstreams := stringutil.CloneSliceOrEmpty(s.conf.UpstreamDNS) upstreamFile := s.conf.UpstreamDNSFileName bootstraps := stringutil.CloneSliceOrEmpty(s.conf.BootstrapDNS) - blockingMode := s.conf.BlockingMode - blockingIPv4 := s.conf.BlockingIPv4 - blockingIPv6 := s.conf.BlockingIPv6 + fallbacks := stringutil.CloneSliceOrEmpty(s.conf.FallbackDNS) + blockingMode, blockingIPv4, blockingIPv6 := s.dnsFilter.BlockingMode() ratelimit := s.conf.Ratelimit customIP := s.conf.EDNSClientSubnet.CustomIP @@ -144,6 +147,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) { Upstreams: &upstreams, UpstreamsFile: &upstreamFile, Bootstraps: &bootstraps, + Fallbacks: &fallbacks, ProtectionEnabled: &protectionEnabled, BlockingMode: &blockingMode, BlockingIPv4: blockingIPv4, @@ -170,7 +174,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) { // handleGetConfig handles requests to the GET /control/dns_info endpoint. func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { resp := s.getDNSConfig() - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } func (req *jsonDNSConfig) checkBlockingMode() (err error) { @@ -208,6 +212,20 @@ func (req *jsonDNSConfig) checkBootstrap() (err error) { return nil } +// checkFallbacks returns an error if any fallback address is invalid. +func (req *jsonDNSConfig) checkFallbacks() (err error) { + if req.Fallbacks == nil { + return nil + } + + err = ValidateUpstreams(*req.Fallbacks) + if err != nil { + return fmt.Errorf("validating fallback servers: %w", err) + } + + return nil +} + // validate returns an error if any field of req is invalid. func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) { if req.Upstreams != nil { @@ -229,6 +247,11 @@ func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) { return err } + err = req.checkFallbacks() + if err != nil { + return err + } + err = req.checkBlockingMode() if err != nil { return err @@ -295,11 +318,11 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) { defer s.serverLock.Unlock() if dc.BlockingMode != nil { - s.conf.BlockingMode = *dc.BlockingMode - if *dc.BlockingMode == BlockingModeCustomIP { - s.conf.BlockingIPv4 = dc.BlockingIPv4.To4() - s.conf.BlockingIPv6 = dc.BlockingIPv6.To16() - } + s.dnsFilter.SetBlockingMode(*dc.BlockingMode, dc.BlockingIPv4, dc.BlockingIPv6) + } + + if dc.ProtectionEnabled != nil { + s.dnsFilter.SetProtectionEnabled(*dc.ProtectionEnabled) } if dc.UpstreamMode != nil { @@ -311,7 +334,6 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) { s.conf.EDNSClientSubnet.CustomIP = dc.EDNSCSCustomIP } - setIfNotNil(&s.conf.ProtectionEnabled, dc.ProtectionEnabled) setIfNotNil(&s.conf.EnableDNSSEC, dc.DNSSECEnabled) setIfNotNil(&s.conf.AAAADisabled, dc.DisableIPv6) @@ -342,6 +364,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.FallbackDNS, dc.Fallbacks), setIfNotNil(&s.conf.EDNSClientSubnet.Enabled, dc.EDNSCSEnabled), setIfNotNil(&s.conf.EDNSClientSubnet.UseCustom, dc.EDNSCSUseCustom), setIfNotNil(&s.conf.CacheSize, dc.CacheSize), @@ -369,6 +392,7 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) { type upstreamJSON struct { Upstreams []string `json:"upstream_dns"` BootstrapDNS []string `json:"bootstrap_dns"` + FallbackDNS []string `json:"fallback_dns"` PrivateUpstreams []string `json:"private_upstream"` } @@ -441,7 +465,7 @@ func ValidateUpstreams(upstreams []string) (err error) { func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) (err error) { conf, err := newUpstreamConfig(upstreams) if err != nil { - return err + return fmt.Errorf("creating config: %w", err) } if conf == nil { @@ -469,11 +493,7 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) } } - if len(errs) > 0 { - return errors.List("checking domain-specific upstreams", errs...) - } - - return nil + return errors.Annotate(errors.Join(errs...), "checking domain-specific upstreams: %w") } var protocols = []string{ @@ -667,10 +687,13 @@ func (s *Server) parseUpstreamLine( PreferIPv6: opts.PreferIPv6, } - if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil { - resolved := s.resolveUpstreamHost(extractUpstreamHost(upstreamAddr)) - sortNetIPAddrs(resolved, opts.PreferIPv6) - opts.ServerIPAddrs = resolved + // dnsFilter can be nil during application update. + if s.dnsFilter != nil { + recs := s.dnsFilter.EtcHostsRecords(extractUpstreamHost(upstreamAddr)) + for _, rec := range recs { + opts.ServerIPAddrs = append(opts.ServerIPAddrs, rec.Addr.AsSlice()) + } + sortNetIPAddrs(opts.ServerIPAddrs, opts.PreferIPv6) } u, err = upstream.AddressToUpstream(upstreamAddr, opts) if err != nil { @@ -728,7 +751,7 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) { req.Upstreams = stringutil.FilterOut(req.Upstreams, IsCommentOrEmpty) req.PrivateUpstreams = stringutil.FilterOut(req.PrivateUpstreams, IsCommentOrEmpty) - upsNum := len(req.Upstreams) + len(req.PrivateUpstreams) + upsNum := len(req.Upstreams) + len(req.FallbackDNS) + len(req.PrivateUpstreams) result := make(map[string]string, upsNum) resCh := make(chan upsCheckResult, upsNum) @@ -740,6 +763,14 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) { } }(ups) } + for _, ups := range req.FallbackDNS { + go func(ups string) { + resCh <- upsCheckResult{ + host: ups, + err: s.checkDNS(ups, opts, checkDNSUpstreamExc), + } + }(ups) + } for _, ups := range req.PrivateUpstreams { go func(ups string) { resCh <- upsCheckResult{ @@ -760,7 +791,7 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) { } } - _ = aghhttp.WriteJSONResponse(w, r, result) + aghhttp.WriteJSONResponseOK(w, r, result) } // handleCacheClear is the handler for the POST /control/cache_clear HTTP API. @@ -806,8 +837,7 @@ func (s *Server) handleSetProtection(w http.ResponseWriter, r *http.Request) { s.serverLock.Lock() defer s.serverLock.Unlock() - s.conf.ProtectionEnabled = protectionReq.Enabled - s.conf.ProtectionDisabledUntil = disabledUntil + s.dnsFilter.SetProtectionStatus(protectionReq.Enabled, disabledUntil) }() s.conf.ConfigModified() diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index 38d8a766..7987d94e 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -58,6 +58,8 @@ const jsonExt = ".json" func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { filterConf := &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, SafeBrowsingEnabled: true, SafeBrowsingCacheSize: 1000, SafeSearchConf: filtering.SafeSearchConfig{Enabled: true}, @@ -68,11 +70,10 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{}, TCPListenAddrs: []*net.TCPAddr{}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, - EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + FallbackDNS: []string{"9.9.9.10"}, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, } @@ -134,6 +135,8 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { filterConf := &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, SafeBrowsingEnabled: true, SafeBrowsingCacheSize: 1000, SafeSearchConf: filtering.SafeSearchConfig{Enabled: true}, @@ -144,11 +147,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{}, TCPListenAddrs: []*net.TCPAddr{}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, - EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, } @@ -177,7 +178,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { wantSet: "", }, { name: "blocking_mode_bad", - wantSet: "blocking_ipv4 must be set when blocking_mode is custom_ip", + wantSet: "blocking_ipv4 must be valid ipv4 on custom_ip blocking_mode", }, { name: "ratelimit", wantSet: "", @@ -225,6 +226,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { }, { name: "local_ptr_upstreams_null", wantSet: "", + }, { + name: "fallbacks", + wantSet: "", }} var data map[string]struct { @@ -243,8 +247,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Cleanup(func() { + s.dnsFilter.SetBlockingMode(filtering.BlockingModeDefault, netip.Addr{}, netip.Addr{}) s.conf = defaultConf - s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{} + s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{} }) rBody := io.NopCloser(bytes.NewReader(caseData.Req)) @@ -405,9 +410,9 @@ func TestValidateUpstreamsPrivate(t *testing.T) { u: "[/128.in-addr.arpa/]#", }, { name: "several_bad", - wantErr: `checking domain-specific upstreams: 2 errors: ` + - `"arpa domain \"1.2.3.4.in-addr.arpa.\" should point to a locally-served network", ` + - `"bad arpa domain name \"non.arpa.\": not a reversed ip network"`, + wantErr: `checking domain-specific upstreams: ` + + `arpa domain "1.2.3.4.in-addr.arpa." should point to a locally-served network` + "\n" + + `bad arpa domain name "non.arpa.": not a reversed ip network`, u: "[/non.arpa/1.2.3.4.in-addr.arpa/127.in-addr.arpa/]#", }, { name: "partial_good", @@ -479,7 +484,6 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) { }).String() hc, err := aghnet.NewHostsContainer( - filtering.SysHostsListID, fstest.MapFS{ hostsFileName: &fstest.MapFile{ Data: []byte(hostsListener.Addr().String() + " " + upstreamHost), @@ -495,12 +499,13 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) { require.NoError(t, err) srv := createTestServer(t, &filtering.Config{ - EtcHosts: hc, + BlockingMode: filtering.BlockingModeDefault, + EtcHosts: hc, }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, UpstreamTimeout: upsTimeout, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, nil) @@ -555,6 +560,23 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) { hostsUps: "OK", }, name: "etc_hosts", + }, { + body: map[string]any{ + "fallback_dns": []string{goodUps}, + }, + wantResp: map[string]any{ + goodUps: "OK", + }, + name: "fallback_success", + }, { + body: map[string]any{ + "fallback_dns": []string{badUps}, + }, + wantResp: map[string]any{ + badUps: `couldn't communicate with upstream: exchanging with ` + + badUps + ` over tcp: dns: id mismatch`, + }, + name: "fallback_broken", }} for _, tc := range testCases { diff --git a/internal/dnsforward/msg.go b/internal/dnsforward/msg.go index 507ae8e8..6aa47103 100644 --- a/internal/dnsforward/msg.go +++ b/internal/dnsforward/msg.go @@ -1,7 +1,7 @@ package dnsforward import ( - "net" + "net/netip" "time" "github.com/AdguardTeam/AdGuardHome/internal/filtering" @@ -9,6 +9,7 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" + "golang.org/x/exp/slices" ) // makeResponse creates a DNS response by req and sets necessary flags. It also @@ -26,24 +27,13 @@ func (s *Server) makeResponse(req *dns.Msg) (resp *dns.Msg) { return resp } -// containsIP returns true if the IP is already in the list. -func containsIP(ips []net.IP, ip net.IP) bool { - for _, a := range ips { - if a.Equal(ip) { - return true - } - } - - return false -} - // ipsFromRules extracts unique non-IP addresses from the filtering result // rules. -func ipsFromRules(resRules []*filtering.ResultRule) (ips []net.IP) { +func ipsFromRules(resRules []*filtering.ResultRule) (ips []netip.Addr) { for _, r := range resRules { // len(resRules) and len(ips) are actually small enough for O(n^2) to do // not raise performance questions. - if ip := r.IP; ip != nil && !containsIP(ips, ip) { + if ip := r.IP; ip != (netip.Addr{}) && !slices.Contains(ips, ip) { ips = append(ips, ip) } } @@ -58,19 +48,21 @@ func (s *Server) genDNSFilterMessage( res *filtering.Result, ) (resp *dns.Msg) { req := dctx.Req - if qt := req.Question[0].Qtype; qt != dns.TypeA && qt != dns.TypeAAAA { - if s.conf.BlockingMode == BlockingModeNullIP { + qt := req.Question[0].Qtype + if qt != dns.TypeA && qt != dns.TypeAAAA { + m, _, _ := s.dnsFilter.BlockingMode() + if m == filtering.BlockingModeNullIP { return s.makeResponse(req) } - return s.genNXDomain(req) + return s.newMsgNODATA(req) } switch res.Reason { case filtering.FilteredSafeBrowsing: - return s.genBlockedHost(req, s.conf.SafeBrowsingBlockHost, dctx) + return s.genBlockedHost(req, s.dnsFilter.SafeBrowsingBlockHost(), dctx) case filtering.FilteredParental: - return s.genBlockedHost(req, s.conf.ParentalBlockHost, dctx) + return s.genBlockedHost(req, s.dnsFilter.ParentalBlockHost(), dctx) case filtering.FilteredSafeSearch: // If Safe Search generated the necessary IP addresses, use them. // Otherwise, if there were no errors, there are no addresses for the @@ -83,36 +75,45 @@ func (s *Server) genDNSFilterMessage( // genForBlockingMode generates a filtered response to req based on the server's // blocking mode. -func (s *Server) genForBlockingMode(req *dns.Msg, ips []net.IP) (resp *dns.Msg) { - qt := req.Question[0].Qtype - switch m := s.conf.BlockingMode; m { - case BlockingModeCustomIP: - switch qt { - case dns.TypeA: - return s.genARecord(req, s.conf.BlockingIPv4) - case dns.TypeAAAA: - return s.genAAAARecord(req, s.conf.BlockingIPv6) - default: - // Generally shouldn't happen, since the types are checked in - // genDNSFilterMessage. - log.Error("dns: invalid msg type %s for blocking mode %s", dns.Type(qt), m) - - return s.makeResponse(req) - } - case BlockingModeDefault: +func (s *Server) genForBlockingMode(req *dns.Msg, ips []netip.Addr) (resp *dns.Msg) { + switch mode, bIPv4, bIPv6 := s.dnsFilter.BlockingMode(); mode { + case filtering.BlockingModeCustomIP: + return s.makeResponseCustomIP(req, bIPv4, bIPv6) + case filtering.BlockingModeDefault: if len(ips) > 0 { return s.genResponseWithIPs(req, ips) } return s.makeResponseNullIP(req) - case BlockingModeNullIP: + case filtering.BlockingModeNullIP: return s.makeResponseNullIP(req) - case BlockingModeNXDOMAIN: + case filtering.BlockingModeNXDOMAIN: return s.genNXDomain(req) - case BlockingModeREFUSED: + case filtering.BlockingModeREFUSED: return s.makeResponseREFUSED(req) default: - log.Error("dns: invalid blocking mode %q", s.conf.BlockingMode) + log.Error("dns: invalid blocking mode %q", mode) + + return s.makeResponse(req) + } +} + +// makeResponseCustomIP generates a DNS response message for Custom IP blocking +// mode with the provided IP addresses and an appropriate resource record type. +func (s *Server) makeResponseCustomIP( + req *dns.Msg, + bIPv4 netip.Addr, + bIPv6 netip.Addr, +) (resp *dns.Msg) { + switch qt := req.Question[0].Qtype; qt { + case dns.TypeA: + return s.genARecord(req, bIPv4) + case dns.TypeAAAA: + return s.genAAAARecord(req, bIPv6) + default: + // Generally shouldn't happen, since the types are checked in + // genDNSFilterMessage. + log.Error("dns: invalid msg type %s for custom IP blocking mode", dns.Type(qt)) return s.makeResponse(req) } @@ -125,13 +126,13 @@ func (s *Server) genServerFailure(request *dns.Msg) *dns.Msg { return &resp } -func (s *Server) genARecord(request *dns.Msg, ip net.IP) *dns.Msg { +func (s *Server) genARecord(request *dns.Msg, ip netip.Addr) *dns.Msg { resp := s.makeResponse(request) resp.Answer = append(resp.Answer, s.genAnswerA(request, ip)) return resp } -func (s *Server) genAAAARecord(request *dns.Msg, ip net.IP) *dns.Msg { +func (s *Server) genAAAARecord(request *dns.Msg, ip netip.Addr) *dns.Msg { resp := s.makeResponse(request) resp.Answer = append(resp.Answer, s.genAnswerAAAA(request, ip)) return resp @@ -141,22 +142,22 @@ func (s *Server) hdr(req *dns.Msg, rrType rules.RRType) (h dns.RR_Header) { return dns.RR_Header{ Name: req.Question[0].Name, Rrtype: rrType, - Ttl: s.conf.BlockedResponseTTL, + Ttl: s.dnsFilter.BlockedResponseTTL(), Class: dns.ClassINET, } } -func (s *Server) genAnswerA(req *dns.Msg, ip net.IP) (ans *dns.A) { +func (s *Server) genAnswerA(req *dns.Msg, ip netip.Addr) (ans *dns.A) { return &dns.A{ Hdr: s.hdr(req, dns.TypeA), - A: ip, + A: ip.AsSlice(), } } -func (s *Server) genAnswerAAAA(req *dns.Msg, ip net.IP) (ans *dns.AAAA) { +func (s *Server) genAnswerAAAA(req *dns.Msg, ip netip.Addr) (ans *dns.AAAA) { return &dns.AAAA{ Hdr: s.hdr(req, dns.TypeAAAA), - AAAA: ip, + AAAA: ip.AsSlice(), } } @@ -203,22 +204,24 @@ func (s *Server) genAnswerTXT(req *dns.Msg, strs []string) (ans *dns.TXT) { // addresses and an appropriate resource record type. If any of the IPs cannot // be converted to the correct protocol, genResponseWithIPs returns an empty // response. -func (s *Server) genResponseWithIPs(req *dns.Msg, ips []net.IP) (resp *dns.Msg) { +func (s *Server) genResponseWithIPs(req *dns.Msg, ips []netip.Addr) (resp *dns.Msg) { var ans []dns.RR switch req.Question[0].Qtype { case dns.TypeA: for _, ip := range ips { - if ip4 := ip.To4(); ip4 == nil { + if ip.Is4() { + ans = append(ans, s.genAnswerA(req, ip)) + } else { ans = nil break } - - ans = append(ans, s.genAnswerA(req, ip)) } case dns.TypeAAAA: for _, ip := range ips { - ans = append(ans, s.genAnswerAAAA(req, ip.To16())) + if ip.Is6() { + ans = append(ans, s.genAnswerAAAA(req, ip)) + } } default: // Go on and return an empty response. @@ -239,9 +242,9 @@ func (s *Server) makeResponseNullIP(req *dns.Msg) (resp *dns.Msg) { // converted into an empty slice instead of the zero IPv4. switch req.Question[0].Qtype { case dns.TypeA: - resp = s.genResponseWithIPs(req, []net.IP{{0, 0, 0, 0}}) + resp = s.genResponseWithIPs(req, []netip.Addr{netip.IPv4Unspecified()}) case dns.TypeAAAA: - resp = s.genResponseWithIPs(req, []net.IP{net.IPv6zero}) + resp = s.genResponseWithIPs(req, []netip.Addr{netip.IPv6Unspecified()}) default: resp = s.makeResponse(req) } @@ -250,9 +253,15 @@ func (s *Server) makeResponseNullIP(req *dns.Msg) (resp *dns.Msg) { } func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSContext) *dns.Msg { - ip := net.ParseIP(newAddr) - if ip != nil { - return s.genResponseWithIPs(request, []net.IP{ip}) + if newAddr == "" { + log.Printf("block host is not specified.") + + return s.genServerFailure(request) + } + + ip, err := netip.ParseAddr(newAddr) + if err == nil { + return s.genResponseWithIPs(request, []netip.Addr{ip}) } // look up the hostname, TODO: cache @@ -274,7 +283,7 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo return s.genServerFailure(request) } - err := prx.Resolve(newContext) + err = prx.Resolve(newContext) if err != nil { log.Printf("couldn't look up replacement host %q: %s", newAddr, err) @@ -314,6 +323,17 @@ func (s *Server) makeResponseREFUSED(request *dns.Msg) *dns.Msg { return &resp } +// newMsgNODATA returns a properly initialized NODATA response. +// +// See https://www.rfc-editor.org/rfc/rfc2308#section-2.2. +func (s *Server) newMsgNODATA(req *dns.Msg) (resp *dns.Msg) { + resp = (&dns.Msg{}).SetRcode(req, dns.RcodeSuccess) + resp.RecursionAvailable = true + resp.Ns = s.genSOA(req) + + return resp +} + func (s *Server) genNXDomain(request *dns.Msg) *dns.Msg { resp := dns.Msg{} resp.SetRcode(request, dns.RcodeNameError) @@ -342,13 +362,13 @@ func (s *Server) genSOA(request *dns.Msg) []dns.RR { Hdr: dns.RR_Header{ Name: zone, Rrtype: dns.TypeSOA, - Ttl: s.conf.BlockedResponseTTL, + Ttl: s.dnsFilter.BlockedResponseTTL(), Class: dns.ClassINET, }, Mbox: "hostmaster.", // zone will be appended later if it's not empty or "." } if soa.Hdr.Ttl == 0 { - soa.Hdr.Ttl = defaultValues.BlockedResponseTTL + soa.Hdr.Ttl = defaultBlockedResponseTTL } if len(zone) > 0 && zone[0] != '.' { soa.Mbox += zone diff --git a/internal/dnsforward/process.go b/internal/dnsforward/process.go index 13a8a2eb..4780c856 100644 --- a/internal/dnsforward/process.go +++ b/internal/dnsforward/process.go @@ -8,7 +8,6 @@ import ( "strings" "time" - "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" @@ -70,20 +69,26 @@ type dnsContext struct { // isLocalClient shows if client's IP address is from locally served // network. isLocalClient bool + + // isDHCPHost is true if the request for a local domain name and the DHCP is + // available for this request. + isDHCPHost bool } // resultCode is the result of a request processing function. type resultCode int const ( - // resultCodeSuccess is returned when a handler performed successfully, - // and the next handler must be called. + // resultCodeSuccess is returned when a handler performed successfully, and + // the next handler must be called. resultCodeSuccess resultCode = iota - // resultCodeFinish is returned when a handler performed successfully, - // and the processing of the request must be stopped. + + // resultCodeFinish is returned when a handler performed successfully, and + // the processing of the request must be stopped. resultCodeFinish - // resultCodeError is returned when a handler failed, and the processing - // of the request must be stopped. + + // resultCodeError is returned when a handler failed, and the processing of + // the request must be stopped. resultCodeError ) @@ -239,70 +244,6 @@ func (s *Server) processClientIP(addr net.Addr) { s.addrProc.Process(clientIP) } -func (s *Server) setTableHostToIP(t hostToIPTable) { - s.tableHostToIPLock.Lock() - defer s.tableHostToIPLock.Unlock() - - s.tableHostToIP = t -} - -func (s *Server) setTableIPToHost(t ipToHostTable) { - s.tableIPToHostLock.Lock() - defer s.tableIPToHostLock.Unlock() - - s.tableIPToHost = t -} - -func (s *Server) onDHCPLeaseChanged(flags int) { - switch flags { - case dhcpd.LeaseChangedAdded, - dhcpd.LeaseChangedAddedStatic, - dhcpd.LeaseChangedRemovedStatic: - // Go on. - case dhcpd.LeaseChangedRemovedAll: - s.setTableHostToIP(nil) - s.setTableIPToHost(nil) - - return - default: - return - } - - ll := s.dhcpServer.Leases(dhcpd.LeasesAll) - hostToIP := make(hostToIPTable, len(ll)) - ipToHost := make(ipToHostTable, len(ll)) - - for _, l := range ll { - // TODO(a.garipov): Remove this after we're finished with the client - // hostname validations in the DHCP server code. - err := netutil.ValidateHostname(l.Hostname) - if err != nil { - log.Debug("dnsforward: skipping invalid hostname %q from dhcp: %s", l.Hostname, err) - - continue - } - - lowhost := strings.ToLower(l.Hostname + "." + s.localDomainSuffix) - - // Assume that we only process IPv4 now. - if !l.IP.Is4() { - log.Debug("dnsforward: skipping invalid ip from dhcp: bad ipv4 net.IP %v", l.IP) - - continue - } - - leaseIP := l.IP - - ipToHost[leaseIP] = lowhost - hostToIP[lowhost] = leaseIP - } - - s.setTableHostToIP(hostToIP) - s.setTableIPToHost(ipToHost) - - log.Debug("dnsforward: added %d a and ptr entries from dhcp", len(ipToHost)) -} - // processDDRQuery responds to Discovery of Designated Resolvers (DDR) SVCB // queries. The response contains different types of encryption supported by // current user configuration. @@ -420,18 +361,6 @@ func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) { return rc } -// dhcpHostToIP tries to get an IP leased by DHCP and returns the copy of -// address since the data inside the internal table may be changed while request -// processing. It's safe for concurrent use. -func (s *Server) dhcpHostToIP(host string) (ip netip.Addr, ok bool) { - s.tableHostToIPLock.Lock() - defer s.tableHostToIPLock.Unlock() - - ip, ok = s.tableHostToIP[host] - - return ip, ok -} - // processDHCPHosts respond to A requests if the target hostname is known to // the server. It responds with a mapped IP address if the DNS64 is enabled and // the request is for AAAA. @@ -443,30 +372,31 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) { pctx := dctx.proxyCtx req := pctx.Req - q := req.Question[0] - reqHost, ok := s.isDHCPClientHostQ(q) - if !ok { + + q := &req.Question[0] + dhcpHost := s.dhcpHostFromRequest(q) + if dctx.isDHCPHost = dhcpHost != ""; !dctx.isDHCPHost { return resultCodeSuccess } if !dctx.isLocalClient { - log.Debug("dnsforward: %q requests for dhcp host %q", pctx.Addr, reqHost) + log.Debug("dnsforward: %q requests for dhcp host %q", pctx.Addr, dhcpHost) pctx.Res = s.genNXDomain(req) // Do not even put into query log. return resultCodeFinish } - ip, ok := s.dhcpHostToIP(reqHost) - if !ok { + ip := s.dhcpServer.IPByHost(dhcpHost) + if ip == (netip.Addr{}) { // Go on and process them with filters, including dnsrewrite ones, and // possibly route them to a domain-specific upstream. - log.Debug("dnsforward: no dhcp record for %q", reqHost) + log.Debug("dnsforward: no dhcp record for %q", dhcpHost) return resultCodeSuccess } - log.Debug("dnsforward: dhcp record for %q is %s", reqHost, ip) + log.Debug("dnsforward: dhcp record for %q is %s", dhcpHost, ip) resp := s.makeResponse(req) switch q.Qtype { @@ -638,17 +568,6 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) { return resultCodeSuccess } -// ipToDHCPHost tries to get a hostname leased by DHCP. It's safe for -// concurrent use. -func (s *Server) ipToDHCPHost(ip netip.Addr) (host string, ok bool) { - s.tableIPToHostLock.Lock() - defer s.tableIPToHostLock.Unlock() - - host, ok = s.tableIPToHost[ip] - - return host, ok -} - // processDHCPAddrs responds to PTR requests if the target IP is leased by the // DHCP server. func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) { @@ -673,12 +592,12 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) { return resultCodeSuccess } - host, ok := s.ipToDHCPHost(ipAddr) - if !ok { + host := s.dhcpServer.HostByIP(ipAddr) + if host == "" { return resultCodeSuccess } - log.Debug("dnsforward: dhcp reverse record for %s is %q", ip, host) + log.Debug("dnsforward: dhcp client %s is %q", ip, host) req := pctx.Req resp := s.makeResponse(req) @@ -686,10 +605,12 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) { Hdr: dns.RR_Header{ Name: req.Question[0].Name, Rrtype: dns.TypePTR, - Ttl: s.conf.BlockedResponseTTL, - Class: dns.ClassINET, + // TODO(e.burkov): Use [dhcpsvc.Lease.Expiry]. See + // https://github.com/AdguardTeam/AdGuardHome/issues/3932. + Ttl: s.dnsFilter.BlockedResponseTTL(), + Class: dns.ClassINET, }, - Ptr: dns.Fqdn(host), + Ptr: dns.Fqdn(strings.Join([]string{host, s.localDomainSuffix}, ".")), } resp.Answer = append(resp.Answer, ptr) pctx.Res = resp @@ -762,10 +683,6 @@ func (s *Server) processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode) s.serverLock.RLock() defer s.serverLock.RUnlock() - if s.dnsFilter == nil { - return resultCodeSuccess - } - var err error if ctx.result, err = s.filterDNSRequest(ctx); err != nil { ctx.err = err @@ -792,17 +709,18 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) { pctx := dctx.proxyCtx req := pctx.Req - q := req.Question[0] + if pctx.Res != nil { // The response has already been set. return resultCodeSuccess - } else if reqHost, ok := s.isDHCPClientHostQ(q); ok { + } else if dctx.isDHCPHost { // A DHCP client hostname query that hasn't been handled or filtered. // Respond with an NXDOMAIN. // // TODO(a.garipov): Route such queries to a custom upstream for the // local domain name if there is one. - log.Debug("dnsforward: dhcp client hostname %q was not filtered", reqHost) + name := req.Question[0].Name + log.Debug("dnsforward: dhcp client hostname %q was not filtered", name[:len(name)-1]) pctx.Res = s.genNXDomain(req) return resultCodeFinish @@ -889,26 +807,26 @@ func (s *Server) setRespAD(pctx *proxy.DNSContext, reqWantsDNSSEC bool) { } } -// isDHCPClientHostQ returns true if q is from a request for a DHCP client -// hostname. If ok is true, reqHost contains the requested hostname. -func (s *Server) isDHCPClientHostQ(q dns.Question) (reqHost string, ok bool) { +// dhcpHostFromRequest returns a hostname from question, if the request is for a +// DHCP client's hostname when DHCP is enabled, and an empty string otherwise. +func (s *Server) dhcpHostFromRequest(q *dns.Question) (reqHost string) { if !s.dhcpServer.Enabled() { - return "", false + return "" } // Include AAAA here, because despite the fact that we don't support it yet, // the expected behavior here is to respond with an empty answer and not // NXDOMAIN. if qt := q.Qtype; qt != dns.TypeA && qt != dns.TypeAAAA { - return "", false + return "" } reqHost = strings.ToLower(q.Name[:len(q.Name)-1]) - if strings.HasSuffix(reqHost, s.localDomainSuffix) { - return reqHost, true + if !netutil.IsImmediateSubdomain(reqHost, s.localDomainSuffix) { + return "" } - return "", false + return reqHost[:len(reqHost)-len(s.localDomainSuffix)-1] } // setCustomUpstream sets custom upstream settings in pctx, if necessary. @@ -972,7 +890,7 @@ func (s *Server) filterAfterResponse(dctx *dnsContext, pctx *proxy.DNSContext) ( // Check the response only if it's from an upstream. Don't check the // response if the protection is disabled since dnsrewrite rules aren't // applied to it anyway. - if !dctx.protectionEnabled || !dctx.responseFromUpstream || s.dnsFilter == nil { + if !dctx.protectionEnabled || !dctx.responseFromUpstream { return resultCodeSuccess } diff --git a/internal/dnsforward/process_internal_test.go b/internal/dnsforward/process_internal_test.go index a45ce245..168a97a1 100644 --- a/internal/dnsforward/process_internal_test.go +++ b/internal/dnsforward/process_internal_test.go @@ -77,13 +77,15 @@ func TestServer_ProcessInitial(t *testing.T) { t.Parallel() c := ServerConfig{ - FilteringConfig: FilteringConfig{ + Config: Config{ AAAADisabled: tc.aaaaDisabled, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, } - s := createTestServer(t, &filtering.Config{}, c, nil) + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, c, nil) var gotAddr netip.Addr s.addrProc = &aghtest.AddressProcessor{ @@ -113,6 +115,101 @@ func TestServer_ProcessInitial(t *testing.T) { } } +func TestServer_ProcessFilteringAfterResponse(t *testing.T) { + t.Parallel() + + var ( + testIPv4 net.IP = netip.MustParseAddr("1.1.1.1").AsSlice() + testIPv6 net.IP = netip.MustParseAddr("1234::cdef").AsSlice() + ) + + testCases := []struct { + name string + req *dns.Msg + aaaaDisabled bool + respAns []dns.RR + wantRC resultCode + wantRespAns []dns.RR + }{{ + name: "pass", + req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS), + aaaaDisabled: false, + respAns: newSVCBHintsAnswer( + aghtest.ReqFQDN, + []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{testIPv4}}, + &dns.SVCBIPv6Hint{Hint: []net.IP{testIPv6}}, + }, + ), + wantRespAns: newSVCBHintsAnswer( + aghtest.ReqFQDN, + []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{testIPv4}}, + &dns.SVCBIPv6Hint{Hint: []net.IP{testIPv6}}, + }, + ), + wantRC: resultCodeSuccess, + }, { + name: "filter", + req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS), + aaaaDisabled: true, + respAns: newSVCBHintsAnswer( + aghtest.ReqFQDN, + []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{testIPv4}}, + &dns.SVCBIPv6Hint{Hint: []net.IP{testIPv6}}, + }, + ), + wantRespAns: newSVCBHintsAnswer( + aghtest.ReqFQDN, + []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{testIPv4}}, + }, + ), + wantRC: resultCodeSuccess, + }} + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + c := ServerConfig{ + Config: Config{ + AAAADisabled: tc.aaaaDisabled, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, + } + + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, c, nil) + + resp := newResp(dns.RcodeSuccess, tc.req, tc.respAns) + dctx := &dnsContext{ + setts: &filtering.Settings{ + FilteringEnabled: true, + ProtectionEnabled: true, + }, + protectionEnabled: true, + responseFromUpstream: true, + result: &filtering.Result{}, + proxyCtx: &proxy.DNSContext{ + Proto: proxy.ProtoUDP, + Req: tc.req, + Res: resp, + Addr: testClientAddr, + }, + } + + gotRC := s.processFilteringAfterResponse(dctx) + assert.Equal(t, tc.wantRC, gotRC) + assert.Equal(t, newResp(dns.RcodeSuccess, tc.req, tc.wantRespAns), dctx.proxyCtx.Res) + }) + } +} + func TestServer_ProcessDDRQuery(t *testing.T) { dohSVCB := &dns.SVCB{ Priority: 1, @@ -245,15 +342,28 @@ func TestServer_ProcessDDRQuery(t *testing.T) { } } +// createTestDNSFilter returns the minimum valid DNSFilter. +func createTestDNSFilter(t *testing.T) (f *filtering.DNSFilter) { + t.Helper() + + f, err := filtering.New(&filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, []filtering.Filter{}) + require.NoError(t, err) + + return f +} + func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled bool) (s *Server) { t.Helper() s = &Server{ + dnsFilter: createTestDNSFilter(t), dnsProxy: &proxy.Proxy{ Config: proxy.Config{}, }, conf: ServerConfig{ - FilteringConfig: FilteringConfig{ + Config: Config{ HandleDDR: ddrEnabled, }, TLSConfig: TLSConfig{ @@ -323,47 +433,59 @@ func TestServer_ProcessDetermineLocal(t *testing.T) { } func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) { + const ( + localDomainSuffix = "lan" + dhcpClient = "example" + + knownHost = dhcpClient + "." + localDomainSuffix + unknownHost = "wronghost." + localDomainSuffix + ) + knownIP := netip.MustParseAddr("1.2.3.4") + dhcp := &testDHCP{ + OnEnabled: func() (_ bool) { return true }, + OnIPByHost: func(host string) (ip netip.Addr) { + if host == dhcpClient { + ip = knownIP + } + + return ip + }, + } + testCases := []struct { wantIP netip.Addr name string host string - wantRes resultCode isLocalCli bool }{{ wantIP: knownIP, name: "local_client_success", - host: "example.lan", - wantRes: resultCodeSuccess, + host: knownHost, isLocalCli: true, }, { wantIP: netip.Addr{}, name: "local_client_unknown_host", - host: "wronghost.lan", - wantRes: resultCodeSuccess, + host: unknownHost, isLocalCli: true, }, { wantIP: netip.Addr{}, name: "external_client_known_host", - host: "example.lan", - wantRes: resultCodeFinish, + host: knownHost, isLocalCli: false, }, { wantIP: netip.Addr{}, name: "external_client_unknown_host", - host: "wronghost.lan", - wantRes: resultCodeFinish, + host: unknownHost, isLocalCli: false, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { s := &Server{ - dhcpServer: testDHCP, - localDomainSuffix: defaultLocalDomainSuffix, - tableHostToIP: hostToIPTable{ - "example." + defaultLocalDomainSuffix: knownIP, - }, + dnsFilter: createTestDNSFilter(t), + dhcpServer: dhcp, + localDomainSuffix: localDomainSuffix, } req := &dns.Msg{ @@ -385,43 +507,52 @@ func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) { } res := s.processDHCPHosts(dctx) - require.Equal(t, tc.wantRes, res) + pctx := dctx.proxyCtx - if tc.wantRes == resultCodeFinish { + if !tc.isLocalCli { + require.Equal(t, resultCodeFinish, res) require.NotNil(t, pctx.Res) assert.Equal(t, dns.RcodeNameError, pctx.Res.Rcode) - assert.Len(t, pctx.Res.Answer, 0) + assert.Empty(t, pctx.Res.Answer) return } + require.Equal(t, resultCodeSuccess, res) + if tc.wantIP == (netip.Addr{}) { assert.Nil(t, pctx.Res) - } else { - require.NotNil(t, pctx.Res) - ans := pctx.Res.Answer - require.Len(t, ans, 1) - - a := testutil.RequireTypeAssert[*dns.A](t, ans[0]) - - ip, err := netutil.IPToAddr(a.A, netutil.AddrFamilyIPv4) - require.NoError(t, err) - - assert.Equal(t, tc.wantIP, ip) + return } + + require.NotNil(t, pctx.Res) + + ans := pctx.Res.Answer + require.Len(t, ans, 1) + + a := testutil.RequireTypeAssert[*dns.A](t, ans[0]) + + ip, err := netutil.IPToAddr(a.A, netutil.AddrFamilyIPv4) + require.NoError(t, err) + + assert.Equal(t, tc.wantIP, ip) }) } } func TestServer_ProcessDHCPHosts(t *testing.T) { const ( - examplecom = "example.com" - examplelan = "example." + defaultLocalDomainSuffix + localTLD = "lan" + + knownClient = "example" + externalHost = knownClient + ".com" + clientHost = knownClient + "." + localTLD ) knownIP := netip.MustParseAddr("1.2.3.4") + testCases := []struct { wantIP netip.Addr name string @@ -431,55 +562,65 @@ func TestServer_ProcessDHCPHosts(t *testing.T) { qtyp uint16 }{{ wantIP: netip.Addr{}, - name: "success_external", - host: examplecom, - suffix: defaultLocalDomainSuffix, + name: "external", + host: externalHost, + suffix: localTLD, wantRes: resultCodeSuccess, qtyp: dns.TypeA, }, { wantIP: netip.Addr{}, - name: "success_external_non_a", - host: examplecom, - suffix: defaultLocalDomainSuffix, + name: "external_non_a", + host: externalHost, + suffix: localTLD, wantRes: resultCodeSuccess, qtyp: dns.TypeCNAME, }, { wantIP: knownIP, - name: "success_internal", - host: examplelan, - suffix: defaultLocalDomainSuffix, + name: "internal", + host: clientHost, + suffix: localTLD, wantRes: resultCodeSuccess, qtyp: dns.TypeA, }, { wantIP: netip.Addr{}, - name: "success_internal_unknown", + name: "internal_unknown", host: "example-new.lan", - suffix: defaultLocalDomainSuffix, + suffix: localTLD, wantRes: resultCodeSuccess, qtyp: dns.TypeA, }, { wantIP: netip.Addr{}, - name: "success_internal_aaaa", - host: examplelan, - suffix: defaultLocalDomainSuffix, + name: "internal_aaaa", + host: clientHost, + suffix: localTLD, wantRes: resultCodeSuccess, qtyp: dns.TypeAAAA, }, { wantIP: knownIP, - name: "success_custom_suffix", - host: "example.custom", + name: "custom_suffix", + host: knownClient + ".custom", suffix: "custom", wantRes: resultCodeSuccess, qtyp: dns.TypeA, }} for _, tc := range testCases { + testDHCP := &testDHCP{ + OnEnabled: func() (_ bool) { return true }, + OnIPByHost: func(host string) (ip netip.Addr) { + if host == knownClient { + ip = knownIP + } + + return ip + }, + OnHostByIP: func(ip netip.Addr) (host string) { panic("not implemented") }, + } + s := &Server{ + dnsFilter: createTestDNSFilter(t), dhcpServer: testDHCP, localDomainSuffix: tc.suffix, - tableHostToIP: hostToIPTable{ - "example." + tc.suffix: knownIP, - }, } req := &dns.Msg{ @@ -504,13 +645,6 @@ func TestServer_ProcessDHCPHosts(t *testing.T) { res := s.processDHCPHosts(dctx) pctx := dctx.proxyCtx assert.Equal(t, tc.wantRes, res) - if tc.wantRes == resultCodeFinish { - require.NotNil(t, pctx.Res) - assert.Equal(t, dns.RcodeNameError, pctx.Res.Rcode) - - return - } - require.NoError(t, dctx.err) if tc.qtyp == dns.TypeAAAA { @@ -555,12 +689,14 @@ func TestServer_ProcessRestrictLocal(t *testing.T) { ), nil }) - s := createTestServer(t, &filtering.Config{}, ServerConfig{ + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, // TODO(s.chzhen): Add tests where EDNSClientSubnet.Enabled is true. - // Improve FilteringConfig declaration for tests. - FilteringConfig: FilteringConfig{ + // Improve Config declaration for tests. + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, ups) @@ -631,11 +767,13 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) { s := createTestServer( t, - &filtering.Config{}, + &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go index b142c86f..18cb9268 100644 --- a/internal/dnsforward/stats.go +++ b/internal/dnsforward/stats.go @@ -139,10 +139,14 @@ func (s *Server) updateStats( clientIP string, ) { pctx := ctx.proxyCtx - e := stats.Entry{ + e := &stats.Entry{ Domain: aghnet.NormalizeDomain(pctx.Req.Question[0].Name), Result: stats.RNotFiltered, - Time: uint32(elapsed / 1000), + Time: elapsed, + } + + if pctx.Upstream != nil { + e.Upstream = pctx.Upstream.Address() } if clientID := ctx.clientID; clientID != "" { diff --git a/internal/dnsforward/stats_test.go b/internal/dnsforward/stats_test.go index 49cfb68a..0b8cae2e 100644 --- a/internal/dnsforward/stats_test.go +++ b/internal/dnsforward/stats_test.go @@ -41,11 +41,11 @@ type testStats struct { // without actually implementing all methods. stats.Interface - lastEntry stats.Entry + lastEntry *stats.Entry } // Update implements the [stats.Interface] interface for *testStats. -func (l *testStats) Update(e stats.Entry) { +func (l *testStats) Update(e *stats.Entry) { if e.Domain == "" { return } diff --git a/internal/dnsforward/svcbmsg_test.go b/internal/dnsforward/svcbmsg_test.go index 8de53988..83645b32 100644 --- a/internal/dnsforward/svcbmsg_test.go +++ b/internal/dnsforward/svcbmsg_test.go @@ -4,6 +4,7 @@ import ( "net" "testing" + "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -12,13 +13,13 @@ import ( func TestGenAnswerHTTPS_andSVCB(t *testing.T) { // Preconditions. - s := &Server{ - conf: ServerConfig{ - FilteringConfig: FilteringConfig{ - BlockedResponseTTL: 3600, - }, + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ + Config: Config{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, - } + }, nil) req := &dns.Msg{ Question: []dns.Question{{ diff --git a/internal/dnsforward/testdata/TestDNSForwardHTTP_handleGetConfig.json b/internal/dnsforward/testdata/TestDNSForwardHTTP_handleGetConfig.json index 11c8766d..d2f4680d 100644 --- a/internal/dnsforward/testdata/TestDNSForwardHTTP_handleGetConfig.json +++ b/internal/dnsforward/testdata/TestDNSForwardHTTP_handleGetConfig.json @@ -11,6 +11,9 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [ + "9.9.9.10" + ], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -43,6 +46,9 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [ + "9.9.9.10" + ], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -75,6 +81,9 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [ + "9.9.9.10" + ], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, diff --git a/internal/dnsforward/testdata/TestDNSForwardHTTP_handleSetConfig.json b/internal/dnsforward/testdata/TestDNSForwardHTTP_handleSetConfig.json index 3404471f..232acba3 100644 --- a/internal/dnsforward/testdata/TestDNSForwardHTTP_handleSetConfig.json +++ b/internal/dnsforward/testdata/TestDNSForwardHTTP_handleSetConfig.json @@ -18,6 +18,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -54,6 +55,7 @@ "bootstrap_dns": [ "9.9.9.10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -91,6 +93,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -128,6 +131,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -165,6 +169,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 6, @@ -202,6 +207,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -241,6 +247,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -280,6 +287,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -317,6 +325,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -354,6 +363,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -391,6 +401,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -428,6 +439,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -467,6 +479,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -506,6 +519,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -544,6 +558,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -581,6 +596,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -620,6 +636,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -662,6 +679,7 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, @@ -699,6 +717,49 @@ "2620:fe::10", "2620:fe::fe:10" ], + "fallback_dns": [], + "protection_enabled": true, + "protection_disabled_until": null, + "ratelimit": 0, + "blocking_mode": "default", + "blocking_ipv4": "", + "blocking_ipv6": "", + "edns_cs_enabled": false, + "dnssec_enabled": false, + "disable_ipv6": false, + "upstream_mode": "", + "cache_size": 0, + "cache_ttl_min": 0, + "cache_ttl_max": 0, + "cache_optimistic": false, + "resolve_clients": false, + "use_private_ptr_resolvers": false, + "local_ptr_upstreams": [], + "edns_cs_use_custom": false, + "edns_cs_custom_ip": "" + } + }, + "fallbacks": { + "req": { + "fallback_dns": [ + "9.9.9.10" + ] + }, + "want": { + "upstream_dns": [ + "8.8.8.8:53", + "8.8.4.4:53" + ], + "upstream_dns_file": "", + "bootstrap_dns": [ + "9.9.9.10", + "149.112.112.10", + "2620:fe::10", + "2620:fe::fe:10" + ], + "fallback_dns": [ + "9.9.9.10" + ], "protection_enabled": true, "protection_disabled_until": null, "ratelimit": 0, diff --git a/internal/dnsforward/upstreams.go b/internal/dnsforward/upstreams.go index 6d1eac1f..0debe8d8 100644 --- a/internal/dnsforward/upstreams.go +++ b/internal/dnsforward/upstreams.go @@ -14,8 +14,6 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" - "github.com/AdguardTeam/urlfilter" - "github.com/miekg/dns" "golang.org/x/exp/maps" "golang.org/x/exp/slices" ) @@ -94,7 +92,8 @@ func (s *Server) prepareUpstreamConfig( uc.Upstreams = defaultUpstreamConfig.Upstreams } - if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil { + // dnsFilter can be nil during application update. + if s.dnsFilter != nil { err = s.replaceUpstreamsWithHosts(uc, opts) if err != nil { return nil, fmt.Errorf("resolving upstreams with hosts: %w", err) @@ -158,17 +157,20 @@ func (s *Server) resolveUpstreamsWithHosts( withIPs, ok := resolved[host] if !ok { - ips := s.resolveUpstreamHost(host) - if len(ips) == 0 { + recs := s.dnsFilter.EtcHostsRecords(host) + if len(recs) == 0 { resolved[host] = nil return nil } - sortNetIPAddrs(ips, opts.PreferIPv6) - withIPs = opts.Clone() - withIPs.ServerIPAddrs = ips + withIPs.ServerIPAddrs = make([]net.IP, 0, len(recs)) + for _, rec := range recs { + withIPs.ServerIPAddrs = append(withIPs.ServerIPAddrs, rec.Addr.AsSlice()) + } + + sortNetIPAddrs(withIPs.ServerIPAddrs, opts.PreferIPv6) resolved[host] = withIPs } else if withIPs == nil { continue @@ -216,33 +218,6 @@ func extractUpstreamHost(addr string) (host string) { return host } -// resolveUpstreamHost returns the version of ups with IP addresses from the -// system hosts file placed into its options. -func (s *Server) resolveUpstreamHost(host string) (addrs []net.IP) { - req := &urlfilter.DNSRequest{ - Hostname: host, - DNSType: dns.TypeA, - } - aRes, _ := s.dnsFilter.EtcHosts.MatchRequest(req) - - req.DNSType = dns.TypeAAAA - aaaaRes, _ := s.dnsFilter.EtcHosts.MatchRequest(req) - - var ips []net.IP - for _, rw := range append(aRes.DNSRewrites(), aaaaRes.DNSRewrites()...) { - dr := rw.DNSRewrite - if dr == nil || dr.Value == nil { - continue - } - - if ip, ok := dr.Value.(net.IP); ok { - ips = append(ips, ip) - } - } - - return ips -} - // sortNetIPAddrs sorts addrs in accordance with the protocol preferences. // Invalid addresses are sorted near the end. // @@ -254,28 +229,38 @@ func sortNetIPAddrs(addrs []net.IP, preferIPv6 bool) { return } - slices.SortStableFunc(addrs, func(addrA, addrB net.IP) (sortsBefore bool) { + slices.SortStableFunc(addrs, func(addrA, addrB net.IP) (res int) { switch len(addrA) { case net.IPv4len, net.IPv6len: switch len(addrB) { case net.IPv4len, net.IPv6len: // Go on. default: - return true + return -1 } default: - return false + return 1 } - if aIs4, bIs4 := addrA.To4() != nil, addrB.To4() != nil; aIs4 != bIs4 { - if aIs4 { - return !preferIPv6 + // Treat IPv6-mapped IPv4 addresses as IPv6 addresses. + aIs4, bIs4 := addrA.To4() != nil, addrB.To4() != nil + if aIs4 == bIs4 { + return bytes.Compare(addrA, addrB) + } + + if aIs4 { + if preferIPv6 { + return 1 } - return preferIPv6 + return -1 } - return bytes.Compare(addrA, addrB) < 0 + if preferIPv6 { + return -1 + } + + return 1 }) } diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go index f403d0ab..cd388853 100644 --- a/internal/filtering/blocked.go +++ b/internal/filtering/blocked.go @@ -50,10 +50,10 @@ func initBlockedServices() { // BlockedServices is the configuration of blocked services. type BlockedServices struct { // Schedule is blocked services schedule for every day of the week. - Schedule *schedule.Weekly `yaml:"schedule"` + Schedule *schedule.Weekly `json:"schedule" yaml:"schedule"` // IDs is the names of blocked services. - IDs []string `yaml:"ids"` + IDs []string `json:"ids" yaml:"ids"` } // Clone returns a deep copy of blocked services. @@ -83,12 +83,12 @@ func (s *BlockedServices) Validate() (err error) { // ApplyBlockedServices - set blocked services settings for this DNS request func (d *DNSFilter) ApplyBlockedServices(setts *Settings) { - d.confLock.RLock() - defer d.confLock.RUnlock() + d.confMu.RLock() + defer d.confMu.RUnlock() setts.ServicesRules = []ServiceEntry{} - bsvc := d.BlockedServices + bsvc := d.conf.BlockedServices // TODO(s.chzhen): Use startTime from [dnsforward.dnsContext]. if !bsvc.Schedule.Contains(time.Now()) { @@ -114,25 +114,37 @@ func (d *DNSFilter) ApplyBlockedServicesList(setts *Settings, list []string) { } func (d *DNSFilter) handleBlockedServicesIDs(w http.ResponseWriter, r *http.Request) { - _ = aghhttp.WriteJSONResponse(w, r, serviceIDs) + aghhttp.WriteJSONResponseOK(w, r, serviceIDs) } func (d *DNSFilter) handleBlockedServicesAll(w http.ResponseWriter, r *http.Request) { - _ = aghhttp.WriteJSONResponse(w, r, struct { + aghhttp.WriteJSONResponseOK(w, r, struct { BlockedServices []blockedService `json:"blocked_services"` }{ BlockedServices: blockedServices, }) } +// handleBlockedServicesList is the handler for the GET +// /control/blocked_services/list HTTP API. +// +// Deprecated: Use handleBlockedServicesGet. func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) { - d.confLock.RLock() - list := d.Config.BlockedServices.IDs - d.confLock.RUnlock() + var list []string + func() { + d.confMu.Lock() + defer d.confMu.Unlock() - _ = aghhttp.WriteJSONResponse(w, r, list) + list = d.conf.BlockedServices.IDs + }() + + aghhttp.WriteJSONResponseOK(w, r, list) } +// handleBlockedServicesSet is the handler for the POST +// /control/blocked_services/set HTTP API. +// +// Deprecated: Use handleBlockedServicesUpdate. func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) { list := []string{} err := json.NewDecoder(r.Body).Decode(&list) @@ -142,11 +154,61 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ return } - d.confLock.Lock() - d.Config.BlockedServices.IDs = list - d.confLock.Unlock() + func() { + d.confMu.Lock() + defer d.confMu.Unlock() - log.Debug("Updated blocked services list: %d", len(list)) + d.conf.BlockedServices.IDs = list + log.Debug("Updated blocked services list: %d", len(list)) + }() - d.Config.ConfigModified() + d.conf.ConfigModified() +} + +// handleBlockedServicesGet is the handler for the GET +// /control/blocked_services/get HTTP API. +func (d *DNSFilter) handleBlockedServicesGet(w http.ResponseWriter, r *http.Request) { + var bsvc *BlockedServices + func() { + d.confMu.RLock() + defer d.confMu.RUnlock() + + bsvc = d.conf.BlockedServices.Clone() + }() + + aghhttp.WriteJSONResponseOK(w, r, bsvc) +} + +// handleBlockedServicesUpdate is the handler for the PUT +// /control/blocked_services/update HTTP API. +func (d *DNSFilter) handleBlockedServicesUpdate(w http.ResponseWriter, r *http.Request) { + bsvc := &BlockedServices{} + err := json.NewDecoder(r.Body).Decode(bsvc) + if err != nil { + aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err) + + return + } + + err = bsvc.Validate() + if err != nil { + aghhttp.Error(r, w, http.StatusUnprocessableEntity, "validating: %s", err) + + return + } + + if bsvc.Schedule == nil { + bsvc.Schedule = schedule.EmptyWeekly() + } + + func() { + d.confMu.Lock() + defer d.confMu.Unlock() + + d.conf.BlockedServices = bsvc + }() + + log.Debug("updated blocked services schedule: %d", len(bsvc.IDs)) + + d.conf.ConfigModified() } diff --git a/internal/filtering/dnsrewrite.go b/internal/filtering/dnsrewrite.go index 58a8dd7b..3fd6e778 100644 --- a/internal/filtering/dnsrewrite.go +++ b/internal/filtering/dnsrewrite.go @@ -1,6 +1,8 @@ package filtering import ( + "github.com/AdguardTeam/golibs/hostsfile" + "github.com/AdguardTeam/urlfilter" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" ) @@ -73,3 +75,59 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) { Reason: RewrittenRule, } } + +// processDNSResultRewrites returns an empty Result if there are no dnsrewrite +// rules in dnsres. Otherwise, it returns the processed Result. +func (d *DNSFilter) processDNSResultRewrites( + dnsres *urlfilter.DNSResult, + host string, +) (dnsRWRes Result) { + dnsr := dnsres.DNSRewrites() + if len(dnsr) == 0 { + return Result{} + } + + res := d.processDNSRewrites(dnsr) + if res.Reason == RewrittenRule && res.CanonName == host { + // A rewrite of a host to itself. Go on and try matching other things. + return Result{} + } + + return res +} + +// appendRewriteResultFromHost appends the rewrite result from rec to vals and +// resRules. +func appendRewriteResultFromHost( + vals []rules.RRValue, + resRules []*ResultRule, + rec *hostsfile.Record, + qtype uint16, +) (updatedVals []rules.RRValue, updatedRules []*ResultRule) { + switch qtype { + case dns.TypeA: + if !rec.Addr.Is4() { + return vals, resRules + } + + vals = append(vals, rec.Addr) + case dns.TypeAAAA: + if !rec.Addr.Is6() { + return vals, resRules + } + + vals = append(vals, rec.Addr) + case dns.TypePTR: + for _, name := range rec.Names { + vals = append(vals, name) + } + } + + recText, _ := rec.MarshalText() + resRules = append(resRules, &ResultRule{ + FilterListID: SysHostsListID, + Text: string(recText), + }) + + return vals, resRules +} diff --git a/internal/filtering/dnsrewrite_test.go b/internal/filtering/dnsrewrite_test.go index c75ea2b9..98853c95 100644 --- a/internal/filtering/dnsrewrite_test.go +++ b/internal/filtering/dnsrewrite_test.go @@ -1,10 +1,17 @@ package filtering import ( - "net" + "fmt" + "net/netip" "path" "testing" + "testing/fstest" + "github.com/AdguardTeam/AdGuardHome/internal/aghnet" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" + "github.com/AdguardTeam/golibs/netutil" + "github.com/AdguardTeam/golibs/testutil" + "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -15,13 +22,13 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { |cname^$dnsrewrite=new-cname |a-record^$dnsrewrite=127.0.0.1 - |aaaa-record^$dnsrewrite=::1 |txt-record^$dnsrewrite=NOERROR;TXT;hello-world - |refused^$dnsrewrite=REFUSED +|mapped^$dnsrewrite=NOERROR;AAAA;::ffff:127.0.0.1 + |a-records^$dnsrewrite=127.0.0.1 |a-records^$dnsrewrite=127.0.0.2 @@ -54,10 +61,11 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { FilteringEnabled: true, } - ipv4p1 := net.IPv4(127, 0, 0, 1) - ipv4p2 := net.IPv4(127, 0, 0, 2) - ipv6p1 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - ipv6p2 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} + ipv4p1 := netutil.IPv4Localhost() + ipv4p2 := ipv4p1.Next() + ipv6p1 := netutil.IPv6Localhost() + ipv6p2 := ipv6p1.Next() + mapped := netip.AddrFrom16(ipv4p1.As16()) testCasesA := []struct { name string @@ -104,6 +112,11 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { want: []any{ipv4p1}, rcode: dns.RcodeSuccess, dtyp: dns.TypeA, + }, { + name: "mapped", + want: []any{mapped}, + rcode: dns.RcodeSuccess, + dtyp: dns.TypeAAAA, }} for _, tc := range testCasesA { @@ -202,3 +215,154 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { assert.Equal(t, "new-ptr-with-dot.", ptr) }) } + +func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) { + addrv4 := netip.MustParseAddr("1.2.3.4") + addrv6 := netip.MustParseAddr("::1") + addrMapped := netip.MustParseAddr("::ffff:1.2.3.4") + + data := fmt.Sprintf( + ""+ + "%s v4.host.example\n"+ + "%s v6.host.example\n"+ + "%s mapped.host.example\n", + addrv4, + addrv6, + addrMapped, + ) + + files := fstest.MapFS{ + "hosts": &fstest.MapFile{ + Data: []byte(data), + }, + } + watcher := &aghtest.FSWatcher{ + OnEvents: func() (e <-chan struct{}) { return nil }, + OnAdd: func(name string) (err error) { return nil }, + OnClose: func() (err error) { return nil }, + } + hc, err := aghnet.NewHostsContainer(files, watcher, "hosts") + require.NoError(t, err) + testutil.CleanupAndRequireSuccess(t, hc.Close) + + f, _ := newForTest(t, &Config{EtcHosts: hc}, nil) + setts := &Settings{ + FilteringEnabled: true, + } + + testCases := []struct { + name string + host string + wantRules []*ResultRule + wantResps []rules.RRValue + dtyp uint16 + }{{ + name: "v4", + host: "v4.host.example", + dtyp: dns.TypeA, + wantRules: []*ResultRule{{ + Text: "1.2.3.4 v4.host.example", + FilterListID: SysHostsListID, + }}, + wantResps: []rules.RRValue{addrv4}, + }, { + name: "v6", + host: "v6.host.example", + dtyp: dns.TypeAAAA, + wantRules: []*ResultRule{{ + Text: "::1 v6.host.example", + FilterListID: SysHostsListID, + }}, + wantResps: []rules.RRValue{addrv6}, + }, { + name: "mapped", + host: "mapped.host.example", + dtyp: dns.TypeAAAA, + wantRules: []*ResultRule{{ + Text: "::ffff:1.2.3.4 mapped.host.example", + FilterListID: SysHostsListID, + }}, + wantResps: []rules.RRValue{addrMapped}, + }, { + name: "ptr", + host: "4.3.2.1.in-addr.arpa", + dtyp: dns.TypePTR, + wantRules: []*ResultRule{{ + Text: "1.2.3.4 v4.host.example", + FilterListID: SysHostsListID, + }}, + wantResps: []rules.RRValue{"v4.host.example"}, + }, { + name: "ptr-mapped", + host: "4.0.3.0.2.0.1.0.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa", + dtyp: dns.TypePTR, + wantRules: []*ResultRule{{ + Text: "::ffff:1.2.3.4 mapped.host.example", + FilterListID: SysHostsListID, + }}, + wantResps: []rules.RRValue{"mapped.host.example"}, + }, { + name: "not_found_v4", + host: "non.existent.example", + dtyp: dns.TypeA, + wantRules: nil, + wantResps: nil, + }, { + name: "not_found_v6", + host: "non.existent.example", + dtyp: dns.TypeAAAA, + wantRules: nil, + wantResps: nil, + }, { + name: "not_found_ptr", + host: "4.3.2.2.in-addr.arpa", + dtyp: dns.TypePTR, + wantRules: nil, + wantResps: nil, + }, { + name: "v4_mismatch", + host: "v4.host.example", + dtyp: dns.TypeAAAA, + wantRules: nil, + wantResps: nil, + }, { + name: "v6_mismatch", + host: "v6.host.example", + dtyp: dns.TypeA, + wantRules: nil, + wantResps: nil, + }, { + name: "wrong_ptr", + host: "4.3.2.1.ip6.arpa", + dtyp: dns.TypePTR, + wantRules: nil, + wantResps: nil, + }, { + name: "unsupported_type", + host: "v4.host.example", + dtyp: dns.TypeCNAME, + wantRules: nil, + wantResps: nil, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var res Result + res, err = f.CheckHost(tc.host, tc.dtyp, setts) + require.NoError(t, err) + + if len(tc.wantRules) == 0 { + assert.Empty(t, res.Rules) + assert.Nil(t, res.DNSRewriteResult) + + return + } + + require.NotNil(t, res.DNSRewriteResult) + require.Contains(t, res.DNSRewriteResult.Response, tc.dtyp) + + assert.Equal(t, tc.wantResps, res.DNSRewriteResult.Response[tc.dtyp]) + assert.Equal(t, tc.wantRules, res.Rules) + }) + } +} diff --git a/internal/filtering/filter.go b/internal/filtering/filter.go index 88e8a0fc..329b6745 100644 --- a/internal/filtering/filter.go +++ b/internal/filtering/filter.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghrenameio" "github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist" "github.com/AdguardTeam/golibs/errors" @@ -54,6 +53,22 @@ func (filter *FilterYAML) Path(dataDir string) string { return filepath.Join(dataDir, filterDir, strconv.FormatInt(filter.ID, 10)+".txt") } +// ensureName sets provided title or default name for the filter if it doesn't +// have name already. +func (filter *FilterYAML) ensureName(title string) { + if filter.Name != "" { + return + } + + if title != "" { + filter.Name = title + + return + } + + filter.Name = fmt.Sprintf("List %d", filter.ID) +} + const ( // errFilterNotExist is returned from [filterSetProperties] when there are // no lists with the desired URL to update. @@ -76,12 +91,12 @@ func (d *DNSFilter) filterSetProperties( newList FilterYAML, isAllowlist bool, ) (shouldRestart bool, err error) { - d.filtersMu.Lock() - defer d.filtersMu.Unlock() + d.conf.filtersMu.Lock() + defer d.conf.filtersMu.Unlock() - filters := d.Filters + filters := d.conf.Filters if isAllowlist { - filters = d.WhitelistFilters + filters = d.conf.WhitelistFilters } i := slices.IndexFunc(filters, func(flt FilterYAML) bool { return flt.URL == listURL }) @@ -147,8 +162,8 @@ func (d *DNSFilter) filterSetProperties( // filterExists returns true if a filter with the same url exists in d. It's // safe for concurrent use. func (d *DNSFilter) filterExists(url string) (ok bool) { - d.filtersMu.RLock() - defer d.filtersMu.RUnlock() + d.conf.filtersMu.RLock() + defer d.conf.filtersMu.RUnlock() r := d.filterExistsLocked(url) @@ -158,13 +173,13 @@ func (d *DNSFilter) filterExists(url string) (ok bool) { // filterExistsLocked returns true if d contains the filter with the same url. // d.filtersMu is expected to be locked. func (d *DNSFilter) filterExistsLocked(url string) (ok bool) { - for _, f := range d.Filters { + for _, f := range d.conf.Filters { if f.URL == url { return true } } - for _, f := range d.WhitelistFilters { + for _, f := range d.conf.WhitelistFilters { if f.URL == url { return true } @@ -179,8 +194,8 @@ func (d *DNSFilter) filterAdd(flt FilterYAML) (err error) { // Defer annotating to unlock sooner. defer func() { err = errors.Annotate(err, "adding filter: %w") }() - d.filtersMu.Lock() - defer d.filtersMu.Unlock() + d.conf.filtersMu.Lock() + defer d.conf.filtersMu.Unlock() // Check for duplicates. if d.filterExistsLocked(flt.URL) { @@ -188,9 +203,9 @@ func (d *DNSFilter) filterAdd(flt FilterYAML) (err error) { } if flt.white { - d.WhitelistFilters = append(d.WhitelistFilters, flt) + d.conf.WhitelistFilters = append(d.conf.WhitelistFilters, flt) } else { - d.Filters = append(d.Filters, flt) + d.conf.Filters = append(d.conf.Filters, flt) } return nil @@ -254,7 +269,7 @@ func (d *DNSFilter) periodicallyRefreshFilters() { ivl := 5 // use a dynamically increasing time interval for { isNetErr, ok := false, false - if d.FiltersUpdateIntervalHours != 0 { + if d.conf.FiltersUpdateIntervalHours != 0 { _, isNetErr, ok = d.tryRefreshFilters(true, true, false) if ok && !isNetErr { ivl = maxInterval @@ -292,8 +307,8 @@ func (d *DNSFilter) tryRefreshFilters(block, allow, force bool) (updated int, is func (d *DNSFilter) listsToUpdate(filters *[]FilterYAML, force bool) (toUpd []FilterYAML) { now := time.Now() - d.filtersMu.RLock() - defer d.filtersMu.RUnlock() + d.conf.filtersMu.RLock() + defer d.conf.filtersMu.RUnlock() for i := range *filters { flt := &(*filters)[i] // otherwise we will be operating on a copy @@ -303,7 +318,7 @@ func (d *DNSFilter) listsToUpdate(filters *[]FilterYAML, force bool) (toUpd []Fi } if !force { - exp := flt.LastUpdated.Add(time.Duration(d.FiltersUpdateIntervalHours) * time.Hour) + exp := flt.LastUpdated.Add(time.Duration(d.conf.FiltersUpdateIntervalHours) * time.Hour) if now.Before(exp) { continue } @@ -349,8 +364,8 @@ func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int, updateCount := 0 - d.filtersMu.Lock() - defer d.filtersMu.Unlock() + d.conf.filtersMu.Lock() + defer d.conf.filtersMu.Unlock() for i := range updateFilters { uf := &updateFilters[i] @@ -412,10 +427,10 @@ func (d *DNSFilter) refreshFiltersIntl(block, allow, force bool) (int, bool) { isNetErr := false if block { - updNum, lists, toUpd, isNetErr = d.refreshFiltersArray(&d.Filters, force) + updNum, lists, toUpd, isNetErr = d.refreshFiltersArray(&d.conf.Filters, force) } if allow { - updNumAl, listsAl, toUpdAl, isNetErrAl := d.refreshFiltersArray(&d.WhitelistFilters, force) + updNumAl, listsAl, toUpdAl, isNetErrAl := d.refreshFiltersArray(&d.conf.WhitelistFilters, force) updNum += updNumAl lists = append(lists, listsAl...) @@ -436,7 +451,7 @@ func (d *DNSFilter) refreshFiltersIntl(block, allow, force bool) (int, bool) { continue } - p := uf.Path(d.DataDir) + p := uf.Path(d.conf.DataDir) err := os.Remove(p + ".old") if err != nil { log.Debug("filtering: removing old filter file %q: %s", p, err) @@ -453,7 +468,7 @@ func (d *DNSFilter) update(filter *FilterYAML) (b bool, err error) { filter.LastUpdated = time.Now() if !b { chErr := os.Chtimes( - filter.Path(d.DataDir), + filter.Path(d.conf.DataDir), filter.LastUpdated, filter.LastUpdated, ) @@ -476,7 +491,7 @@ func (d *DNSFilter) updateIntl(flt *FilterYAML) (ok bool, err error) { // users. // // See https://github.com/AdguardTeam/AdGuardHome/issues/3198. - tmpFile, err := aghrenameio.NewPendingFile(flt.Path(d.DataDir), 0o644) + tmpFile, err := aghrenameio.NewPendingFile(flt.Path(d.conf.DataDir), 0o644) if err != nil { return false, err } @@ -517,7 +532,7 @@ func (d *DNSFilter) finalizeUpdate( return errors.WithDeferred(returned, file.Cleanup()) } - log.Info("filtering: saving contents of filter %d into %q", id, flt.Path(d.DataDir)) + log.Info("filtering: saving contents of filter %d into %q", id, flt.Path(d.conf.DataDir)) err = file.CloseReplace() if err != nil { @@ -527,7 +542,7 @@ func (d *DNSFilter) finalizeUpdate( rulesCount := res.RulesCount log.Info("filtering: updated filter %d: %d bytes, %d rules", id, res.BytesWritten, rulesCount) - flt.Name = aghalg.Coalesce(flt.Name, res.Title) + flt.ensureName(res.Title) flt.checksum = res.Checksum flt.RulesCount = rulesCount @@ -557,7 +572,7 @@ func (d *DNSFilter) reader(fltURL string) (r io.ReadCloser, err error) { // readerFromURL returns an io.ReadCloser reading filtering-rule list data form // the filter's URL. func (d *DNSFilter) readerFromURL(fltURL string) (r io.ReadCloser, err error) { - resp, err := d.HTTPClient.Get(fltURL) + resp, err := d.conf.HTTPClient.Get(fltURL) if err != nil { // Don't wrap the error since it's informative enough as is. return nil, err @@ -572,7 +587,7 @@ func (d *DNSFilter) readerFromURL(fltURL string) (r io.ReadCloser, err error) { // loads filter contents from the file in dataDir func (d *DNSFilter) load(flt *FilterYAML) (err error) { - fileName := flt.Path(d.DataDir) + fileName := flt.Path(d.conf.DataDir) log.Debug("filtering: loading filter %d from %q", flt.ID, fileName) @@ -601,45 +616,46 @@ func (d *DNSFilter) load(flt *FilterYAML) (err error) { return fmt.Errorf("parsing filter file: %w", err) } + flt.ensureName(res.Title) flt.RulesCount, flt.checksum, flt.LastUpdated = res.RulesCount, res.Checksum, st.ModTime() return nil } func (d *DNSFilter) EnableFilters(async bool) { - d.filtersMu.RLock() - defer d.filtersMu.RUnlock() + d.conf.filtersMu.RLock() + defer d.conf.filtersMu.RUnlock() d.enableFiltersLocked(async) } func (d *DNSFilter) enableFiltersLocked(async bool) { - filters := make([]Filter, 1, len(d.Filters)+len(d.WhitelistFilters)+1) + filters := make([]Filter, 1, len(d.conf.Filters)+len(d.conf.WhitelistFilters)+1) filters[0] = Filter{ ID: CustomListID, - Data: []byte(strings.Join(d.UserRules, "\n")), + Data: []byte(strings.Join(d.conf.UserRules, "\n")), } - for _, filter := range d.Filters { + for _, filter := range d.conf.Filters { if !filter.Enabled { continue } filters = append(filters, Filter{ ID: filter.ID, - FilePath: filter.Path(d.DataDir), + FilePath: filter.Path(d.conf.DataDir), }) } var allowFilters []Filter - for _, filter := range d.WhitelistFilters { + for _, filter := range d.conf.WhitelistFilters { if !filter.Enabled { continue } allowFilters = append(allowFilters, Filter{ ID: filter.ID, - FilePath: filter.Path(d.DataDir), + FilePath: filter.Path(d.conf.DataDir), }) } @@ -648,5 +664,5 @@ func (d *DNSFilter) enableFiltersLocked(async bool) { log.Error("filtering: enabling filters: %s", err) } - d.SetEnabled(d.FilteringEnabled) + d.SetEnabled(d.conf.FilteringEnabled) } diff --git a/internal/filtering/filter_test.go b/internal/filtering/filter_test.go index 53e846fc..229b7a9b 100644 --- a/internal/filtering/filter_test.go +++ b/internal/filtering/filter_test.go @@ -1,7 +1,6 @@ package filtering import ( - "io/fs" "net" "net/http" "net/url" @@ -16,6 +15,9 @@ import ( "github.com/stretchr/testify/require" ) +// testTimeout is the common timeout for tests. +const testTimeout = 5 * time.Second + // serveHTTPLocally starts a new HTTP server, that handles its index with h. It // also gracefully closes the listener when the test under t finishes. func serveHTTPLocally(t *testing.T, h http.Handler) (urlStr string) { @@ -50,7 +52,49 @@ func serveFiltersLocally(t *testing.T, fltContent []byte) (urlStr string) { })) } -func TestFilters(t *testing.T) { +// updateAndAssert loads filter content from its URL and then asserts rules +// count. +func updateAndAssert( + t *testing.T, + dnsFilter *DNSFilter, + f *FilterYAML, + wantUpd require.BoolAssertionFunc, + wantRulesCount int, +) { + t.Helper() + + ok, err := dnsFilter.update(f) + require.NoError(t, err) + wantUpd(t, ok) + + assert.Equal(t, wantRulesCount, f.RulesCount) + + dir, err := os.ReadDir(filepath.Join(dnsFilter.conf.DataDir, filterDir)) + require.NoError(t, err) + require.FileExists(t, f.Path(dnsFilter.conf.DataDir)) + + assert.Len(t, dir, 1) + + err = dnsFilter.load(f) + require.NoError(t, err) +} + +// newDNSFilter returns a new properly initialized DNS filter instance. +func newDNSFilter(t *testing.T) (d *DNSFilter) { + t.Helper() + + dnsFilter, err := New(&Config{ + DataDir: t.TempDir(), + HTTPClient: &http.Client{ + Timeout: testTimeout, + }, + }, nil) + require.NoError(t, err) + + return dnsFilter +} + +func TestDNSFilter_Update(t *testing.T) { const content = `||example.org^$third-party # Inline comment example ||example.com^$third-party @@ -58,49 +102,20 @@ func TestFilters(t *testing.T) { ` fltContent := []byte(content) - addr := serveFiltersLocally(t, fltContent) - - tempDir := t.TempDir() - - filters, err := New(&Config{ - DataDir: tempDir, - HTTPClient: &http.Client{ - Timeout: 5 * time.Second, - }, - }, nil) - require.NoError(t, err) - f := &FilterYAML{ - URL: addr, + URL: addr, + Name: "test-filter", } - updateAndAssert := func(t *testing.T, want require.BoolAssertionFunc, wantRulesCount int) { - var ok bool - ok, err = filters.update(f) - require.NoError(t, err) - want(t, ok) - - assert.Equal(t, wantRulesCount, f.RulesCount) - - var dir []fs.DirEntry - dir, err = os.ReadDir(filepath.Join(tempDir, filterDir)) - require.NoError(t, err) - - assert.Len(t, dir, 1) - - require.FileExists(t, f.Path(tempDir)) - - err = filters.load(f) - require.NoError(t, err) - } + dnsFilter := newDNSFilter(t) t.Run("download", func(t *testing.T) { - updateAndAssert(t, require.True, 3) + updateAndAssert(t, dnsFilter, f, require.True, 3) }) t.Run("refresh_idle", func(t *testing.T) { - updateAndAssert(t, require.False, 3) + updateAndAssert(t, dnsFilter, f, require.False, 3) }) t.Run("refresh_actually", func(t *testing.T) { @@ -110,13 +125,51 @@ func TestFilters(t *testing.T) { f.URL = serveFiltersLocally(t, anotherContent) t.Cleanup(func() { f.URL = oldURL }) - updateAndAssert(t, require.True, 1) + updateAndAssert(t, dnsFilter, f, require.True, 1) }) t.Run("load_unload", func(t *testing.T) { - err = filters.load(f) + err := dnsFilter.load(f) require.NoError(t, err) f.unload() }) } + +func TestFilterYAML_EnsureName(t *testing.T) { + dnsFilter := newDNSFilter(t) + + t.Run("title_custom", func(t *testing.T) { + content := []byte("! Title: src-title\n||example.com^") + + f := &FilterYAML{ + URL: serveFiltersLocally(t, content), + Name: "user-custom", + } + + updateAndAssert(t, dnsFilter, f, require.True, 1) + assert.Equal(t, "user-custom", f.Name) + }) + + t.Run("title_from_src", func(t *testing.T) { + content := []byte("! Title: src-title\n||example.com^") + + f := &FilterYAML{ + URL: serveFiltersLocally(t, content), + } + + updateAndAssert(t, dnsFilter, f, require.True, 1) + assert.Equal(t, "src-title", f.Name) + }) + + t.Run("title_default", func(t *testing.T) { + content := []byte("||example.com^") + + f := &FilterYAML{ + URL: serveFiltersLocally(t, content), + } + + updateAndAssert(t, dnsFilter, f, require.True, 1) + assert.Equal(t, "List 0", f.Name) + }) +} diff --git a/internal/filtering/filtering.go b/internal/filtering/filtering.go index b6249cee..c16897c0 100644 --- a/internal/filtering/filtering.go +++ b/internal/filtering/filtering.go @@ -7,6 +7,7 @@ import ( "io/fs" "net" "net/http" + "net/netip" "os" "path/filepath" "runtime" @@ -14,13 +15,16 @@ import ( "strings" "sync" "sync/atomic" + "time" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/hostsfile" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/mathutil" + "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/urlfilter" "github.com/AdguardTeam/urlfilter/filterlist" @@ -51,7 +55,7 @@ type ServiceEntry struct { // Settings are custom filtering settings for a client. type Settings struct { ClientName string - ClientIP net.IP + ClientIP netip.Addr ClientTags []string ServicesRules []ServiceEntry @@ -73,35 +77,19 @@ type Resolver interface { // Config allows you to configure DNS filtering with New() or just change variables directly. type Config struct { + // BlockingIPv4 is the IP address to be returned for a blocked A request. + BlockingIPv4 netip.Addr `yaml:"blocking_ipv4"` + + // BlockingIPv6 is the IP address to be returned for a blocked AAAA request. + BlockingIPv6 netip.Addr `yaml:"blocking_ipv6"` + // SafeBrowsingChecker is the safe browsing hash-prefix checker. SafeBrowsingChecker Checker `yaml:"-"` // ParentControl is the parental control hash-prefix checker. ParentalControlChecker Checker `yaml:"-"` - // enabled is used to be returned within Settings. - // - // It is of type uint32 to be accessed by atomic. - // - // TODO(e.burkov): Use atomic.Bool in Go 1.19. - enabled uint32 - - FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists - FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours) - - ParentalEnabled bool `yaml:"parental_enabled"` - SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` - - SafeBrowsingCacheSize uint `yaml:"safebrowsing_cache_size"` // (in bytes) - SafeSearchCacheSize uint `yaml:"safesearch_cache_size"` // (in bytes) - ParentalCacheSize uint `yaml:"parental_cache_size"` // (in bytes) - // TODO(a.garipov): Use timeutil.Duration - CacheTime uint `yaml:"cache_time"` // Element's TTL (in minutes) - - SafeSearchConf SafeSearchConfig `yaml:"safe_search"` - SafeSearch SafeSearch `yaml:"-"` - - Rewrites []*LegacyRewrite `yaml:"rewrites"` + SafeSearch SafeSearch `yaml:"-"` // BlockedServices is the configuration of blocked services. // Per-client settings can override this configuration. @@ -120,11 +108,30 @@ type Config struct { // HTTPClient is the client to use for updating the remote filters. HTTPClient *http.Client `yaml:"-"` + // filtersMu protects filter lists. + filtersMu *sync.RWMutex + + // ProtectionDisabledUntil is the timestamp until when the protection is + // disabled. + ProtectionDisabledUntil *time.Time `yaml:"protection_disabled_until"` + + SafeSearchConf SafeSearchConfig `yaml:"safe_search"` + // DataDir is used to store filters' contents. DataDir string `yaml:"-"` - // filtersMu protects filter lists. - filtersMu *sync.RWMutex + // BlockingMode defines the way how blocked responses are constructed. + BlockingMode BlockingMode `yaml:"blocking_mode"` + + // 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"` + + Rewrites []*LegacyRewrite `yaml:"rewrites"` // Filters are the blocking filter lists. Filters []FilterYAML `yaml:"-"` @@ -134,8 +141,62 @@ type Config struct { // UserRules is the global list of custom rules. UserRules []string `yaml:"-"` + + SafeBrowsingCacheSize uint `yaml:"safebrowsing_cache_size"` // (in bytes) + SafeSearchCacheSize uint `yaml:"safesearch_cache_size"` // (in bytes) + ParentalCacheSize uint `yaml:"parental_cache_size"` // (in bytes) + // TODO(a.garipov): Use timeutil.Duration + CacheTime uint `yaml:"cache_time"` // Element's TTL (in minutes) + + // enabled is used to be returned within Settings. + // + // It is of type uint32 to be accessed by atomic. + // + // TODO(e.burkov): Use atomic.Bool in Go 1.19. + enabled uint32 + + // FiltersUpdateIntervalHours is the time period to update filters + // (in hours). + FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` + + // BlockedResponseTTL is the time-to-live value for blocked responses. If + // 0, then default value is used (3600). + BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` + + // FilteringEnabled indicates whether or not use filter lists. + FilteringEnabled bool `yaml:"filtering_enabled"` + + ParentalEnabled bool `yaml:"parental_enabled"` + SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` + + // ProtectionEnabled defines whether or not use any of filtering features. + ProtectionEnabled bool `yaml:"protection_enabled"` } +// BlockingMode is an enum of all allowed blocking modes. +type BlockingMode string + +// Allowed blocking modes. +const ( + // BlockingModeCustomIP means respond with a custom IP address. + BlockingModeCustomIP BlockingMode = "custom_ip" + + // BlockingModeDefault is the same as BlockingModeNullIP for + // Adblock-style rules, but responds with the IP address specified in + // the rule when blocked by an `/etc/hosts`-style rule. + BlockingModeDefault BlockingMode = "default" + + // BlockingModeNullIP means respond with a zero IP address: "0.0.0.0" + // for A requests and "::" for AAAA ones. + BlockingModeNullIP BlockingMode = "null_ip" + + // BlockingModeNXDOMAIN means respond with the NXDOMAIN code. + BlockingModeNXDOMAIN BlockingMode = "nxdomain" + + // BlockingModeREFUSED means respond with the REFUSED code. + BlockingModeREFUSED BlockingMode = "refused" +) + // LookupStats store stats collected during safebrowsing or parental checks type LookupStats struct { Requests uint64 // number of HTTP requests that were sent @@ -189,9 +250,11 @@ type DNSFilter struct { engineLock sync.RWMutex - Config // for direct access by library users, even a = assignment - // confLock protects Config. - confLock sync.RWMutex + // confMu protects conf. + confMu *sync.RWMutex + + // conf contains filtering parameters. + conf *Config // Channel for passing data to filters-initializer goroutine filtersInitializerChan chan filtersInitializerParams @@ -292,48 +355,38 @@ func (r Reason) In(reasons ...Reason) (ok bool) { return slices.Contains(reasons // SetEnabled sets the status of the *DNSFilter. func (d *DNSFilter) SetEnabled(enabled bool) { - atomic.StoreUint32(&d.enabled, mathutil.BoolToNumber[uint32](enabled)) + atomic.StoreUint32(&d.conf.enabled, mathutil.BoolToNumber[uint32](enabled)) } // Settings returns filtering settings. func (d *DNSFilter) Settings() (s *Settings) { - d.confLock.RLock() - defer d.confLock.RUnlock() + d.confMu.RLock() + defer d.confMu.RUnlock() return &Settings{ - FilteringEnabled: atomic.LoadUint32(&d.Config.enabled) != 0, - SafeSearchEnabled: d.Config.SafeSearchConf.Enabled, - SafeBrowsingEnabled: d.Config.SafeBrowsingEnabled, - ParentalEnabled: d.Config.ParentalEnabled, + FilteringEnabled: atomic.LoadUint32(&d.conf.enabled) != 0, + SafeSearchEnabled: d.conf.SafeSearchConf.Enabled, + SafeBrowsingEnabled: d.conf.SafeBrowsingEnabled, + ParentalEnabled: d.conf.ParentalEnabled, } } // WriteDiskConfig - write configuration func (d *DNSFilter) WriteDiskConfig(c *Config) { func() { - d.confLock.Lock() - defer d.confLock.Unlock() + d.confMu.Lock() + defer d.confMu.Unlock() - *c = d.Config + *c = *d.conf c.Rewrites = cloneRewrites(c.Rewrites) }() - d.filtersMu.RLock() - defer d.filtersMu.RUnlock() + d.conf.filtersMu.RLock() + defer d.conf.filtersMu.RUnlock() - c.Filters = slices.Clone(d.Filters) - c.WhitelistFilters = slices.Clone(d.WhitelistFilters) - c.UserRules = slices.Clone(d.UserRules) -} - -// cloneRewrites returns a deep copy of entries. -func cloneRewrites(entries []*LegacyRewrite) (clone []*LegacyRewrite) { - clone = make([]*LegacyRewrite, len(entries)) - for i, rw := range entries { - clone[i] = rw.clone() - } - - return clone + c.Filters = slices.Clone(d.conf.Filters) + c.WhitelistFilters = slices.Clone(d.conf.WhitelistFilters) + c.UserRules = slices.Clone(d.conf.UserRules) } // setFilters sets new filters, synchronously or asynchronously. When filters @@ -405,13 +458,84 @@ func (d *DNSFilter) reset() { } } +// ProtectionStatus returns the status of protection and time until it's +// disabled if so. +func (d *DNSFilter) ProtectionStatus() (status bool, disabledUntil *time.Time) { + d.confMu.RLock() + defer d.confMu.RUnlock() + + return d.conf.ProtectionEnabled, d.conf.ProtectionDisabledUntil +} + +// SetProtectionStatus updates the status of protection and time until it's +// disabled. +func (d *DNSFilter) SetProtectionStatus(status bool, disabledUntil *time.Time) { + d.confMu.Lock() + defer d.confMu.Unlock() + + d.conf.ProtectionEnabled = status + d.conf.ProtectionDisabledUntil = disabledUntil +} + +// SetProtectionEnabled updates the status of protection. +func (d *DNSFilter) SetProtectionEnabled(status bool) { + d.confMu.Lock() + defer d.confMu.Unlock() + + d.conf.ProtectionEnabled = status +} + +// EtcHostsRecords returns the hosts records for the hostname. +func (d *DNSFilter) EtcHostsRecords(hostname string) (recs []*hostsfile.Record) { + if d.conf.EtcHosts != nil { + return d.conf.EtcHosts.MatchName(hostname) + } + + return recs +} + +// SetBlockingMode sets blocking mode properties. +func (d *DNSFilter) SetBlockingMode(mode BlockingMode, bIPv4, bIPv6 netip.Addr) { + d.confMu.Lock() + defer d.confMu.Unlock() + + d.conf.BlockingMode = mode + if mode == BlockingModeCustomIP { + d.conf.BlockingIPv4 = bIPv4 + d.conf.BlockingIPv6 = bIPv6 + } +} + +// BlockingMode returns blocking mode properties. +func (d *DNSFilter) BlockingMode() (mode BlockingMode, bIPv4, bIPv6 netip.Addr) { + d.confMu.RLock() + defer d.confMu.RUnlock() + + return d.conf.BlockingMode, d.conf.BlockingIPv4, d.conf.BlockingIPv6 +} + +// BlockedResponseTTL returns TTL for blocked responses. +func (d *DNSFilter) BlockedResponseTTL() (ttl uint32) { + return d.conf.BlockedResponseTTL +} + +// SafeBrowsingBlockHost returns a host for safe browsing blocked responses. +func (d *DNSFilter) SafeBrowsingBlockHost() (host string) { + return d.conf.SafeBrowsingBlockHost +} + +// ParentalBlockHost returns a host for parental protection blocked responses. +func (d *DNSFilter) ParentalBlockHost() (host string) { + return d.conf.ParentalBlockHost +} + // ResultRule contains information about applied rules. type ResultRule struct { // Text is the text of the rule. Text string `json:",omitempty"` // IP is the host IP. It is nil unless the rule uses the // /etc/hosts syntax or the reason is FilteredSafeSearch. - IP net.IP `json:",omitempty"` + IP netip.Addr `json:",omitempty"` // FilterListID is the ID of the rule's filter list. FilterListID int64 `json:",omitempty"` } @@ -437,7 +561,7 @@ type Result struct { // IPList is the lookup rewrite result. It is empty unless Reason is set to // Rewritten. - IPList []net.IP `json:",omitempty"` + IPList []netip.Addr `json:",omitempty"` // Rules are applied rules. If Rules are not empty, each rule is not nil. Rules []*ResultRule `json:",omitempty"` @@ -503,31 +627,50 @@ func (d *DNSFilter) matchSysHosts( qtype uint16, setts *Settings, ) (res Result, err error) { - if !setts.FilteringEnabled || d.EtcHosts == nil { + // TODO(e.burkov): Where else is this checked? + if !setts.FilteringEnabled || d.conf.EtcHosts == nil { return res, nil } - dnsres, _ := d.EtcHosts.MatchRequest(&urlfilter.DNSRequest{ - Hostname: host, - SortedClientTags: setts.ClientTags, - // TODO(e.burkov): Wait for urlfilter update to pass netip.Addr. - ClientIP: setts.ClientIP.String(), - ClientName: setts.ClientName, - DNSType: qtype, - }) - if dnsres == nil { - return res, nil + var recs []*hostsfile.Record + switch qtype { + case dns.TypeA, dns.TypeAAAA: + recs = d.conf.EtcHosts.MatchName(host) + case dns.TypePTR: + var ip net.IP + ip, err = netutil.IPFromReversedAddr(host) + if err != nil { + log.Debug("filtering: failed to parse PTR record %q: %s", host, err) + + return res, nil + } + + addr, _ := netip.AddrFromSlice(ip) + recs = d.conf.EtcHosts.MatchAddr(addr) + default: + log.Debug("filtering: unsupported query type %s", dns.Type(qtype)) } - dnsr := dnsres.DNSRewrites() - if len(dnsr) == 0 { - return res, nil + var vals []rules.RRValue + var resRules []*ResultRule + resRulesLen := 0 + for _, rec := range recs { + vals, resRules = appendRewriteResultFromHost(vals, resRules, rec, qtype) + if len(resRules) > resRulesLen { + resRulesLen = len(resRules) + log.Debug("filtering: matched %s in %q", host, rec.Source) + } } - res = d.processDNSRewrites(dnsr) - res.Reason = RewrittenAutoHosts - for _, r := range res.Rules { - r.Text = stringutil.Coalesce(d.EtcHosts.Translate(r.Text), r.Text) + if len(vals) > 0 { + res.DNSRewriteResult = &DNSRewriteResult{ + Response: DNSRewriteResultResponse{ + qtype: vals, + }, + RCode: dns.RcodeSuccess, + } + res.Rules = resRules + res.Reason = RewrittenRule } return res, nil @@ -543,10 +686,10 @@ func (d *DNSFilter) matchSysHosts( // accordingly. If the found rewrite has a special value of "A" or "AAAA", the // result is an exception. func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) { - d.confLock.RLock() - defer d.confLock.RUnlock() + d.confMu.RLock() + defer d.confMu.RUnlock() - rewrites, matched := findRewrites(d.Rewrites, host, qtype) + rewrites, matched := findRewrites(d.conf.Rewrites, host, qtype) if !matched { return Result{} } @@ -586,7 +729,7 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) { cnames.Add(host) res.CanonName = host - rewrites, matched = findRewrites(d.Rewrites, host, qtype) + rewrites, matched = findRewrites(d.conf.Rewrites, host, qtype) } setRewriteResult(&res, host, rewrites, qtype) @@ -594,25 +737,6 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) { return res } -// setRewriteResult sets the Reason or IPList of res if necessary. res must not -// be nil. -func setRewriteResult(res *Result, host string, rewrites []*LegacyRewrite, qtype uint16) { - for _, rw := range rewrites { - if rw.Type == qtype && (qtype == dns.TypeA || qtype == dns.TypeAAAA) { - if rw.IP == nil { - // "A"/"AAAA" exception: allow getting from upstream. - res.Reason = NotFilteredNotFound - - return - } - - res.IPList = append(res.IPList, rw.IP) - - log.Debug("rewrite: a/aaaa for %s is %s", host, rw.IP) - } - } -} - // matchBlockedServicesRules checks the host against the blocked services rules // in settings, if any. The err is always nil, it is only there to make this // a valid hostChecker function. @@ -794,22 +918,27 @@ func (d *DNSFilter) matchHostProcessDNSResult( return makeResult([]rules.Rule{dnsres.NetworkRule}, reason) } - if qtype == dns.TypeA && dnsres.HostRulesV4 != nil { - res = makeResult(hostRulesToRules(dnsres.HostRulesV4), FilteredBlockList) - for i, hr := range dnsres.HostRulesV4 { - res.Rules[i].IP = hr.IP.To4() + switch qtype { + case dns.TypeA: + if dnsres.HostRulesV4 != nil { + res = makeResult(hostRulesToRules(dnsres.HostRulesV4), FilteredBlockList) + for i, hr := range dnsres.HostRulesV4 { + res.Rules[i].IP = hr.IP + } + + return res } + case dns.TypeAAAA: + if dnsres.HostRulesV6 != nil { + res = makeResult(hostRulesToRules(dnsres.HostRulesV6), FilteredBlockList) + for i, hr := range dnsres.HostRulesV6 { + res.Rules[i].IP = hr.IP + } - return res - } - - if qtype == dns.TypeAAAA && dnsres.HostRulesV6 != nil { - res = makeResult(hostRulesToRules(dnsres.HostRulesV6), FilteredBlockList) - for i, hr := range dnsres.HostRulesV6 { - res.Rules[i].IP = hr.IP.To16() + return res } - - return res + default: + // Go on. } return hostResultForOtherQType(dnsres) @@ -844,7 +973,7 @@ func (d *DNSFilter) matchHost( Hostname: host, SortedClientTags: setts.ClientTags, // TODO(e.burkov): Wait for urlfilter update to pass net.IP. - ClientIP: setts.ClientIP.String(), + ClientIP: setts.ClientIP, ClientName: setts.ClientName, DNSType: rrtype, } @@ -895,26 +1024,6 @@ func (d *DNSFilter) matchHost( return res, nil } -// processDNSResultRewrites returns an empty Result if there are no dnsrewrite -// rules in dnsres. Otherwise, it returns the processed Result. -func (d *DNSFilter) processDNSResultRewrites( - dnsres *urlfilter.DNSResult, - host string, -) (dnsRWRes Result) { - dnsr := dnsres.DNSRewrites() - if len(dnsr) == 0 { - return Result{} - } - - res := d.processDNSRewrites(dnsr) - if res.Reason == RewrittenRule && res.CanonName == host { - // A rewrite of a host to itself. Go on and try matching other things. - return Result{} - } - - return res -} - // makeResult returns a properly constructed Result. func makeResult(matchedRules []rules.Rule, reason Reason) (res Result) { resRules := make([]*ResultRule, len(matchedRules)) @@ -951,6 +1060,7 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) { refreshLock: &sync.Mutex{}, safeBrowsingChecker: c.SafeBrowsingChecker, parentalControlChecker: c.ParentalControlChecker, + confMu: &sync.RWMutex{}, } d.safeSearch = c.SafeSearch @@ -977,17 +1087,16 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) { defer func() { err = errors.Annotate(err, "filtering: %w") }() - d.Config = *c - d.filtersMu = &sync.RWMutex{} + d.conf = c + d.conf.filtersMu = &sync.RWMutex{} err = d.prepareRewrites() if err != nil { return nil, fmt.Errorf("rewrites: preparing: %s", err) } - if d.BlockedServices != nil { - err = d.BlockedServices.Validate() - + if d.conf.BlockedServices != nil { + err = d.conf.BlockedServices.Validate() if err != nil { return nil, fmt.Errorf("filtering: %w", err) } @@ -1002,16 +1111,16 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) { } } - _ = os.MkdirAll(filepath.Join(d.DataDir, filterDir), 0o755) + _ = os.MkdirAll(filepath.Join(d.conf.DataDir, filterDir), 0o755) - d.loadFilters(d.Filters) - d.loadFilters(d.WhitelistFilters) + d.loadFilters(d.conf.Filters) + d.loadFilters(d.conf.WhitelistFilters) - d.Filters = deduplicateFilters(d.Filters) - d.WhitelistFilters = deduplicateFilters(d.WhitelistFilters) + d.conf.Filters = deduplicateFilters(d.conf.Filters) + d.conf.WhitelistFilters = deduplicateFilters(d.conf.WhitelistFilters) - updateUniqueFilterID(d.Filters) - updateUniqueFilterID(d.WhitelistFilters) + updateUniqueFilterID(d.conf.Filters) + updateUniqueFilterID(d.conf.WhitelistFilters) return d, nil } diff --git a/internal/filtering/filtering_test.go b/internal/filtering/filtering_test.go index e7b55d6f..83018ab3 100644 --- a/internal/filtering/filtering_test.go +++ b/internal/filtering/filtering_test.go @@ -3,12 +3,13 @@ package filtering import ( "bytes" "fmt" - "net" + "net/netip" "testing" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/filtering/hashprefix" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" @@ -91,7 +92,7 @@ func (d *DNSFilter) checkMatchEmpty(t *testing.T, hostname string, setts *Settin assert.Falsef(t, res.IsFiltered, "host %q", hostname) } -func TestEtcHostsMatching(t *testing.T) { +func TestDNSFilter_CheckHost_hostRules(t *testing.T) { addr := "216.239.38.120" addr6 := "::1" text := fmt.Sprintf(` %s google.com www.google.com # enforce google's safesearch @@ -149,8 +150,8 @@ func TestEtcHostsMatching(t *testing.T) { require.Len(t, res.Rules, 2) - assert.Equal(t, res.Rules[0].IP, net.IP{0, 0, 0, 1}) - assert.Equal(t, res.Rules[1].IP, net.IP{0, 0, 0, 2}) + assert.Equal(t, res.Rules[0].IP, netip.AddrFrom4([4]byte{0, 0, 0, 1})) + assert.Equal(t, res.Rules[1].IP, netip.AddrFrom4([4]byte{0, 0, 0, 2})) // One IPv6 address. res, err = d.CheckHost("host2", dns.TypeAAAA, setts) @@ -160,7 +161,7 @@ func TestEtcHostsMatching(t *testing.T) { require.Len(t, res.Rules, 1) - assert.Equal(t, res.Rules[0].IP, net.IPv6loopback) + assert.Equal(t, res.Rules[0].IP, netutil.IPv6Localhost()) } // Safe Browsing. diff --git a/internal/filtering/hashprefix/hashprefix_internal_test.go b/internal/filtering/hashprefix/hashprefix_internal_test.go index 7e724010..97290661 100644 --- a/internal/filtering/hashprefix/hashprefix_internal_test.go +++ b/internal/filtering/hashprefix/hashprefix_internal_test.go @@ -27,7 +27,7 @@ func TestChcker_getQuestion(t *testing.T) { hashes := hostnameToHashes("1.2.3.sub.host.com") assert.Len(t, hashes, 3) - hash := sha256.Sum256([]byte("3.sub.host.com")) + hash := hostnameHash(sha256.Sum256([]byte("3.sub.host.com"))) hexPref1 := hex.EncodeToString(hash[:prefixLen]) assert.True(t, slices.Contains(hashes, hash)) @@ -105,7 +105,7 @@ func TestChecker_storeInCache(t *testing.T) { // store in cache hashes for "3.sub.host.com" and "host.com" // and empty data for hash-prefix for "sub.host.com" hashes := []hostnameHash{} - hash := sha256.Sum256([]byte("sub.host.com")) + hash := hostnameHash(sha256.Sum256([]byte("sub.host.com"))) hashes = append(hashes, hash) var hashesArray []hostnameHash hash4 := sha256.Sum256([]byte("3.sub.host.com")) diff --git a/internal/filtering/http.go b/internal/filtering/http.go index 8d3f202f..ca6b8cf9 100644 --- a/internal/filtering/http.go +++ b/internal/filtering/http.go @@ -3,8 +3,8 @@ package filtering import ( "encoding/json" "fmt" - "net" "net/http" + "net/netip" "net/url" "os" "path/filepath" @@ -124,7 +124,7 @@ func (d *DNSFilter) handleFilteringAddURL(w http.ResponseWriter, r *http.Request return } - d.ConfigModified() + d.conf.ConfigModified() d.EnableFilters(true) _, err = fmt.Fprintf(w, "OK %d rules\n", filt.RulesCount) @@ -149,12 +149,12 @@ func (d *DNSFilter) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ var deleted FilterYAML func() { - d.filtersMu.Lock() - defer d.filtersMu.Unlock() + d.conf.filtersMu.Lock() + defer d.conf.filtersMu.Unlock() - filters := &d.Filters + filters := &d.conf.Filters if req.Whitelist { - filters = &d.WhitelistFilters + filters = &d.conf.WhitelistFilters } delIdx := slices.IndexFunc(*filters, func(flt FilterYAML) bool { @@ -167,7 +167,7 @@ func (d *DNSFilter) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ } deleted = (*filters)[delIdx] - p := deleted.Path(d.DataDir) + p := deleted.Path(d.conf.DataDir) err = os.Rename(p, p+".old") if err != nil && !errors.Is(err, os.ErrNotExist) { log.Error("deleting filter %d: renaming file %q: %s", deleted.ID, p, err) @@ -180,7 +180,7 @@ func (d *DNSFilter) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ log.Info("deleted filter %d", deleted.ID) }() - d.ConfigModified() + d.conf.ConfigModified() d.EnableFilters(true) // NOTE: The old files "filter.txt.old" aren't deleted. It's not really @@ -242,7 +242,7 @@ func (d *DNSFilter) handleFilteringSetURL(w http.ResponseWriter, r *http.Request return } - d.ConfigModified() + d.conf.ConfigModified() if restart { d.EnableFilters(true) } @@ -266,8 +266,8 @@ func (d *DNSFilter) handleFilteringSetRules(w http.ResponseWriter, r *http.Reque return } - d.UserRules = req.Rules - d.ConfigModified() + d.conf.UserRules = req.Rules + d.conf.ConfigModified() d.EnableFilters(true) } @@ -301,7 +301,7 @@ func (d *DNSFilter) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques return } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } type filterJSON struct { @@ -340,21 +340,21 @@ func filterToJSON(f FilterYAML) filterJSON { // Get filtering configuration func (d *DNSFilter) handleFilteringStatus(w http.ResponseWriter, r *http.Request) { resp := filteringConfig{} - d.filtersMu.RLock() - resp.Enabled = d.FilteringEnabled - resp.Interval = d.FiltersUpdateIntervalHours - for _, f := range d.Filters { + d.conf.filtersMu.RLock() + resp.Enabled = d.conf.FilteringEnabled + resp.Interval = d.conf.FiltersUpdateIntervalHours + for _, f := range d.conf.Filters { fj := filterToJSON(f) resp.Filters = append(resp.Filters, fj) } - for _, f := range d.WhitelistFilters { + for _, f := range d.conf.WhitelistFilters { fj := filterToJSON(f) resp.WhitelistFilters = append(resp.WhitelistFilters, fj) } - resp.UserRules = d.UserRules - d.filtersMu.RUnlock() + resp.UserRules = d.conf.UserRules + d.conf.filtersMu.RUnlock() - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // Set filtering configuration @@ -374,14 +374,14 @@ func (d *DNSFilter) handleFilteringConfig(w http.ResponseWriter, r *http.Request } func() { - d.filtersMu.Lock() - defer d.filtersMu.Unlock() + d.conf.filtersMu.Lock() + defer d.conf.filtersMu.Unlock() - d.FilteringEnabled = req.Enabled - d.FiltersUpdateIntervalHours = req.Interval + d.conf.FilteringEnabled = req.Enabled + d.conf.FiltersUpdateIntervalHours = req.Interval }() - d.ConfigModified() + d.conf.ConfigModified() d.EnableFilters(true) } @@ -404,8 +404,8 @@ type checkHostResp struct { SvcName string `json:"service_name"` // for Rewrite: - CanonName string `json:"cname"` // CNAME value - IPList []net.IP `json:"ip_addrs"` // list of IP addresses + CanonName string `json:"cname"` // CNAME value + IPList []netip.Addr `json:"ip_addrs"` // list of IP addresses // FilterID is the ID of the rule's filter list. // @@ -456,7 +456,7 @@ func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) { } } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // setProtectedBool sets the value of a boolean pointer under a lock. l must @@ -484,15 +484,15 @@ func protectedBool(mu *sync.RWMutex, ptr *bool) (val bool) { // handleSafeBrowsingEnable is the handler for the POST // /control/safebrowsing/enable HTTP API. func (d *DNSFilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) { - setProtectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled, true) - d.Config.ConfigModified() + setProtectedBool(d.confMu, &d.conf.SafeBrowsingEnabled, true) + d.conf.ConfigModified() } // handleSafeBrowsingDisable is the handler for the POST // /control/safebrowsing/disable HTTP API. func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) { - setProtectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled, false) - d.Config.ConfigModified() + setProtectedBool(d.confMu, &d.conf.SafeBrowsingEnabled, false) + d.conf.ConfigModified() } // handleSafeBrowsingStatus is the handler for the GET @@ -501,24 +501,24 @@ func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Requ resp := &struct { Enabled bool `json:"enabled"` }{ - Enabled: protectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled), + Enabled: protectedBool(d.confMu, &d.conf.SafeBrowsingEnabled), } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // handleParentalEnable is the handler for the POST /control/parental/enable // HTTP API. func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) { - setProtectedBool(&d.confLock, &d.Config.ParentalEnabled, true) - d.Config.ConfigModified() + setProtectedBool(d.confMu, &d.conf.ParentalEnabled, true) + d.conf.ConfigModified() } // handleParentalDisable is the handler for the POST /control/parental/disable // HTTP API. func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) { - setProtectedBool(&d.confLock, &d.Config.ParentalEnabled, false) - d.Config.ConfigModified() + setProtectedBool(d.confMu, &d.conf.ParentalEnabled, false) + d.conf.ConfigModified() } // handleParentalStatus is the handler for the GET /control/parental/status @@ -527,15 +527,15 @@ func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) resp := &struct { Enabled bool `json:"enabled"` }{ - Enabled: protectedBool(&d.confLock, &d.Config.ParentalEnabled), + Enabled: protectedBool(d.confMu, &d.conf.ParentalEnabled), } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // RegisterFilteringHandlers - register handlers func (d *DNSFilter) RegisterFilteringHandlers() { - registerHTTP := d.HTTPRegister + registerHTTP := d.conf.HTTPRegister if registerHTTP == nil { return } @@ -560,9 +560,14 @@ func (d *DNSFilter) RegisterFilteringHandlers() { registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesIDs) registerHTTP(http.MethodGet, "/control/blocked_services/all", d.handleBlockedServicesAll) + + // Deprecated handlers. registerHTTP(http.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList) registerHTTP(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet) + registerHTTP(http.MethodGet, "/control/blocked_services/get", d.handleBlockedServicesGet) + registerHTTP(http.MethodPut, "/control/blocked_services/update", d.handleBlockedServicesUpdate) + registerHTTP(http.MethodGet, "/control/filtering/status", d.handleFilteringStatus) registerHTTP(http.MethodPost, "/control/filtering/config", d.handleFilteringConfig) registerHTTP(http.MethodPost, "/control/filtering/add_url", d.handleFilteringAddURL) diff --git a/internal/filtering/rewrite/item_internal_test.go b/internal/filtering/rewrite/item_internal_test.go index 68d88223..a63b2cea 100644 --- a/internal/filtering/rewrite/item_internal_test.go +++ b/internal/filtering/rewrite/item_internal_test.go @@ -18,9 +18,9 @@ func TestItem_equal(t *testing.T) { } testCases := []struct { - name string left *Item right *Item + name string want bool }{{ name: "nil_left", diff --git a/internal/filtering/rewrite/storage_test.go b/internal/filtering/rewrite/storage_test.go index 4db06a4e..502c20b9 100644 --- a/internal/filtering/rewrite/storage_test.go +++ b/internal/filtering/rewrite/storage_test.go @@ -1,9 +1,10 @@ package rewrite import ( - "net" + "net/netip" "testing" + "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/urlfilter" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" @@ -45,33 +46,43 @@ func TestDefaultStorage_CRUD(t *testing.T) { } func TestDefaultStorage_MatchRequest(t *testing.T) { + var ( + addr1v4 = netip.AddrFrom4([4]byte{1, 2, 3, 4}) + addr2v4 = netip.AddrFrom4([4]byte{1, 2, 3, 5}) + addr3v4 = netip.AddrFrom4([4]byte{1, 2, 3, 6}) + addr4v4 = netip.AddrFrom4([4]byte{1, 2, 3, 7}) + + addr1v6 = netip.MustParseAddr("1:2:3::4") + addr2v6 = netip.MustParseAddr("1234::5678") + ) + items := []*Item{{ // This one and below are about CNAME, A and AAAA. Domain: "somecname", Answer: "somehost.com", }, { Domain: "somehost.com", - Answer: "0.0.0.0", + Answer: netip.IPv4Unspecified().String(), }, { Domain: "host.com", - Answer: "1.2.3.4", + Answer: addr1v4.String(), }, { Domain: "host.com", - Answer: "1.2.3.5", + Answer: addr2v4.String(), }, { Domain: "host.com", - Answer: "1:2:3::4", + Answer: addr1v6.String(), }, { Domain: "www.host.com", Answer: "host.com", }, { // This one is a wildcard. Domain: "*.host.com", - Answer: "1.2.3.5", + Answer: addr2v4.String(), }, { // This one and below are about wildcard overriding. Domain: "a.host.com", - Answer: "1.2.3.4", + Answer: addr1v4.String(), }, { // This one is about CNAME and wildcard interacting. Domain: "*.host2.com", @@ -89,13 +100,13 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { Answer: "x.host.com", }, { Domain: "*.hostboth.com", - Answer: "1.2.3.6", + Answer: addr3v4.String(), }, { Domain: "*.hostboth.com", - Answer: "1234::5678", + Answer: addr2v6.String(), }, { Domain: "BIGHOST.COM", - Answer: "1.2.3.7", + Answer: addr4v4.String(), }, { Domain: "*.issue4016.com", Answer: "sub.issue4016.com", @@ -123,12 +134,12 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { name: "rewritten_a", host: "www.host.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{1, 2, 3, 4}.To16(), + Value: addr1v4, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, }, { - Value: net.IP{1, 2, 3, 5}.To16(), + Value: addr2v4, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -138,7 +149,7 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { name: "rewritten_aaaa", host: "www.host.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.ParseIP("1:2:3::4"), + Value: addr1v6, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeAAAA, @@ -148,7 +159,7 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { name: "wildcard_match", host: "abc.host.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{1, 2, 3, 5}.To16(), + Value: addr2v4, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -169,12 +180,12 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { name: "wildcard_cname_interaction", host: "www.host2.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{1, 2, 3, 4}.To16(), + Value: addr1v4, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, }, { - Value: net.IP{1, 2, 3, 5}.To16(), + Value: addr2v4, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -184,7 +195,7 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { name: "two_cnames", host: "b.host.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{0, 0, 0, 0}.To16(), + Value: netip.IPv4Unspecified(), NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -194,7 +205,7 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { name: "two_cnames_and_wildcard", host: "b.host3.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{1, 2, 3, 5}.To16(), + Value: addr2v4, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -204,7 +215,7 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { name: "issue3343", host: "www.hostboth.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.ParseIP("1234::5678"), + Value: addr2v6, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeAAAA, @@ -214,7 +225,7 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { name: "issue3351", host: "bighost.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{1, 2, 3, 7}.To16(), + Value: addr4v4, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -255,16 +266,22 @@ func TestDefaultStorage_MatchRequest(t *testing.T) { } func TestDefaultStorage_MatchRequest_Levels(t *testing.T) { + var ( + addr1 = netip.AddrFrom4([4]byte{1, 1, 1, 1}) + addr2 = netip.AddrFrom4([4]byte{2, 2, 2, 2}) + addr3 = netip.AddrFrom4([4]byte{3, 3, 3, 3}) + ) + // Exact host, wildcard L2, wildcard L3. items := []*Item{{ Domain: "host.com", - Answer: "1.1.1.1", + Answer: addr1.String(), }, { Domain: "*.host.com", - Answer: "2.2.2.2", + Answer: addr2.String(), }, { Domain: "*.sub.host.com", - Answer: "3.3.3.3", + Answer: addr3.String(), }} s, err := NewDefaultStorage(-1, items) @@ -279,7 +296,7 @@ func TestDefaultStorage_MatchRequest_Levels(t *testing.T) { name: "exact_match", host: "host.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{1, 1, 1, 1}.To16(), + Value: addr1, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -289,7 +306,7 @@ func TestDefaultStorage_MatchRequest_Levels(t *testing.T) { name: "l2_match", host: "sub.host.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{2, 2, 2, 2}.To16(), + Value: addr2, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -300,7 +317,7 @@ func TestDefaultStorage_MatchRequest_Levels(t *testing.T) { // name: "l3_match", // host: "my.sub.host.com", // wantDNSRewrites: []*rules.DNSRewrite{{ - // Value: net.IP{3, 3, 3, 3}.To16(), + // Value: addr3, // NewCNAME: "", // RCode: dns.RcodeSuccess, // RRType: dns.TypeA, @@ -321,10 +338,12 @@ func TestDefaultStorage_MatchRequest_Levels(t *testing.T) { } func TestDefaultStorage_MatchRequest_ExceptionCNAME(t *testing.T) { + addr := netip.AddrFrom4([4]byte{2, 2, 2, 2}) + // Wildcard and exception for a sub-domain. items := []*Item{{ Domain: "*.host.com", - Answer: "2.2.2.2", + Answer: addr.String(), }, { Domain: "sub.host.com", Answer: "sub.host.com", @@ -345,7 +364,7 @@ func TestDefaultStorage_MatchRequest_ExceptionCNAME(t *testing.T) { name: "match_subdomain", host: "my.host.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{2, 2, 2, 2}.To16(), + Value: addr, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -377,16 +396,18 @@ func TestDefaultStorage_MatchRequest_ExceptionCNAME(t *testing.T) { } func TestDefaultStorage_MatchRequest_ExceptionIP(t *testing.T) { + addr := netip.AddrFrom4([4]byte{1, 2, 3, 4}) + // Exception for AAAA record. items := []*Item{{ Domain: "host.com", - Answer: "1.2.3.4", + Answer: addr.String(), }, { Domain: "host.com", Answer: "AAAA", }, { Domain: "host2.com", - Answer: "::1", + Answer: netutil.IPv6Localhost().String(), }, { Domain: "host2.com", Answer: "A", @@ -407,7 +428,7 @@ func TestDefaultStorage_MatchRequest_ExceptionIP(t *testing.T) { name: "match_A", host: "host.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.IP{1, 2, 3, 4}.To16(), + Value: addr, NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -427,7 +448,7 @@ func TestDefaultStorage_MatchRequest_ExceptionIP(t *testing.T) { name: "match_AAAA_host2.com", host: "host2.com", wantDNSRewrites: []*rules.DNSRewrite{{ - Value: net.ParseIP("::1"), + Value: netutil.IPv6Localhost(), NewCNAME: "", RCode: dns.RcodeSuccess, RRType: dns.TypeAAAA, diff --git a/internal/filtering/rewritehttp.go b/internal/filtering/rewritehttp.go index fea5a650..ed34bb4b 100644 --- a/internal/filtering/rewritehttp.go +++ b/internal/filtering/rewritehttp.go @@ -15,22 +15,27 @@ type rewriteEntryJSON struct { Answer string `json:"answer"` } +// handleRewriteList is the handler for the GET /control/rewrite/list HTTP API. func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) { arr := []*rewriteEntryJSON{} - d.confLock.Lock() - for _, ent := range d.Config.Rewrites { - jsent := rewriteEntryJSON{ - Domain: ent.Domain, - Answer: ent.Answer, - } - arr = append(arr, &jsent) - } - d.confLock.Unlock() + func() { + d.confMu.RLock() + defer d.confMu.RUnlock() - _ = aghhttp.WriteJSONResponse(w, r, arr) + for _, ent := range d.conf.Rewrites { + jsonEnt := rewriteEntryJSON{ + Domain: ent.Domain, + Answer: ent.Answer, + } + arr = append(arr, &jsonEnt) + } + }() + + aghhttp.WriteJSONResponseOK(w, r, arr) } +// handleRewriteAdd is the handler for the POST /control/rewrite/add HTTP API. func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { rwJSON := rewriteEntryJSON{} err := json.NewDecoder(r.Body).Decode(&rwJSON) @@ -54,14 +59,24 @@ func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { return } - d.confLock.Lock() - d.Config.Rewrites = append(d.Config.Rewrites, rw) - d.confLock.Unlock() - log.Debug("rewrite: added element: %s -> %s [%d]", rw.Domain, rw.Answer, len(d.Config.Rewrites)) + func() { + d.confMu.Lock() + defer d.confMu.Unlock() - d.Config.ConfigModified() + d.conf.Rewrites = append(d.conf.Rewrites, rw) + log.Debug( + "rewrite: added element: %s -> %s [%d]", + rw.Domain, + rw.Answer, + len(d.conf.Rewrites), + ) + }() + + d.conf.ConfigModified() } +// handleRewriteDelete is the handler for the POST /control/rewrite/delete HTTP +// API. func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) { jsent := rewriteEntryJSON{} err := json.NewDecoder(r.Body).Decode(&jsent) @@ -77,20 +92,23 @@ func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) } arr := []*LegacyRewrite{} - d.confLock.Lock() - for _, ent := range d.Config.Rewrites { - if ent.equal(entDel) { - log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer) + func() { + d.confMu.Lock() + defer d.confMu.Unlock() - continue + for _, ent := range d.conf.Rewrites { + if ent.equal(entDel) { + log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer) + + continue + } + + arr = append(arr, ent) } + d.conf.Rewrites = arr + }() - arr = append(arr, ent) - } - d.Config.Rewrites = arr - d.confLock.Unlock() - - d.Config.ConfigModified() + d.conf.ConfigModified() } // rewriteUpdateJSON is a struct for JSON object with rewrite rule update info. @@ -132,21 +150,21 @@ func (d *DNSFilter) handleRewriteUpdate(w http.ResponseWriter, r *http.Request) index := -1 defer func() { if index >= 0 { - d.Config.ConfigModified() + d.conf.ConfigModified() } }() - d.confLock.Lock() - defer d.confLock.Unlock() + d.confMu.Lock() + defer d.confMu.Unlock() - index = slices.IndexFunc(d.Config.Rewrites, rwDel.equal) + index = slices.IndexFunc(d.conf.Rewrites, rwDel.equal) if index == -1 { aghhttp.Error(r, w, http.StatusBadRequest, "target rule not found") return } - d.Config.Rewrites = slices.Replace(d.Config.Rewrites, index, index+1, rwAdd) + d.conf.Rewrites = slices.Replace(d.conf.Rewrites, index, index+1, rwAdd) log.Debug("rewrite: removed element: %s -> %s", rwDel.Domain, rwDel.Answer) log.Debug("rewrite: added element: %s -> %s", rwAdd.Domain, rwAdd.Answer) diff --git a/internal/filtering/rewrites.go b/internal/filtering/rewrites.go index e3625c84..fa1b7774 100644 --- a/internal/filtering/rewrites.go +++ b/internal/filtering/rewrites.go @@ -2,10 +2,11 @@ package filtering import ( "fmt" - "net" + "net/netip" "strings" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/mathutil" "github.com/miekg/dns" "golang.org/x/exp/slices" @@ -26,22 +27,12 @@ type LegacyRewrite struct { // IP is the IP address that should be used in the response if Type is // dns.TypeA or dns.TypeAAAA. - IP net.IP `yaml:"-"` + IP netip.Addr `yaml:"-"` // Type is the DNS record type: A, AAAA, or CNAME. Type uint16 `yaml:"-"` } -// clone returns a deep clone of rw. -func (rw *LegacyRewrite) clone() (cloneRW *LegacyRewrite) { - return &LegacyRewrite{ - Domain: rw.Domain, - Answer: rw.Answer, - IP: slices.Clone(rw.IP), - Type: rw.Type, - } -} - // equal returns true if the rw is equal to the other. func (rw *LegacyRewrite) equal(other *LegacyRewrite) (ok bool) { return rw.Domain == other.Domain && rw.Answer == other.Answer @@ -61,11 +52,11 @@ func (rw *LegacyRewrite) matchesQType(qt uint16) (ok bool) { // If the types match or the entry is set to allow only the other type, // include them. - return rw.Type == qt || rw.IP == nil + return rw.Type == qt || rw.IP == netip.Addr{} } -// normalize makes sure that the a new or decoded entry is normalized with -// regards to domain name case, IP length, and so on. +// normalize makes sure that the new or decoded entry is normalized with regards +// to domain name case, IP length, and so on. // // If rw is nil, it returns an errors. func (rw *LegacyRewrite) normalize() (err error) { @@ -80,12 +71,12 @@ func (rw *LegacyRewrite) normalize() (err error) { switch rw.Answer { case "AAAA": - rw.IP = nil + rw.IP = netip.Addr{} rw.Type = dns.TypeAAAA return nil case "A": - rw.IP = nil + rw.IP = netip.Addr{} rw.Type = dns.TypeA return nil @@ -93,19 +84,18 @@ func (rw *LegacyRewrite) normalize() (err error) { // Go on. } - ip := net.ParseIP(rw.Answer) - if ip == nil { + ip, err := netip.ParseAddr(rw.Answer) + if err != nil { + log.Debug("normalizing legacy rewrite: %s", err) rw.Type = dns.TypeCNAME return nil } - ip4 := ip.To4() - if ip4 != nil { - rw.IP = ip4 + rw.IP = ip + if ip.Is4() { rw.Type = dns.TypeA } else { - rw.IP = ip rw.Type = dns.TypeAAAA } @@ -122,29 +112,34 @@ func matchDomainWildcard(host, wildcard string) (ok bool) { return isWildcard(wildcard) && strings.HasSuffix(host, wildcard[1:]) } -// legacyRewriteSortsBefore sorts rewrites according to the following priority: +// Compare is used to sort rewrites according to the following priority: // // 1. A and AAAA > CNAME; // 2. wildcard > exact; // 3. lower level wildcard > higher level wildcard; -func legacyRewriteSortsBefore(a, b *LegacyRewrite) (sortsBefore bool) { - if a.Type == dns.TypeCNAME && b.Type != dns.TypeCNAME { - return true - } else if a.Type != dns.TypeCNAME && b.Type == dns.TypeCNAME { - return false +func (rw *LegacyRewrite) Compare(b *LegacyRewrite) (res int) { + if rw.Type == dns.TypeCNAME && b.Type != dns.TypeCNAME { + return -1 + } else if rw.Type != dns.TypeCNAME && b.Type == dns.TypeCNAME { + return 1 } - if aIsWld, bIsWld := isWildcard(a.Domain), isWildcard(b.Domain); aIsWld != bIsWld { - return bIsWld + aIsWld, bIsWld := isWildcard(rw.Domain), isWildcard(b.Domain) + if aIsWld == bIsWld { + // Both are either wildcards or both aren't. + return len(rw.Domain) - len(b.Domain) } - // Both are either wildcards or both aren't. - return len(a.Domain) > len(b.Domain) + if aIsWld { + return 1 + } + + return -1 } // prepareRewrites normalizes and validates all legacy DNS rewrites. func (d *DNSFilter) prepareRewrites() (err error) { - for i, r := range d.Rewrites { + for i, r := range d.conf.Rewrites { err = r.normalize() if err != nil { return fmt.Errorf("at index %d: %w", i, err) @@ -181,7 +176,7 @@ func findRewrites( return nil, matched } - slices.SortFunc(rewrites, legacyRewriteSortsBefore) + slices.SortFunc(rewrites, (*LegacyRewrite).Compare) for i, r := range rewrites { if isWildcard(r.Domain) { @@ -195,3 +190,37 @@ func findRewrites( return rewrites, matched } + +// setRewriteResult sets the Reason or IPList of res if necessary. res must not +// be nil. +func setRewriteResult(res *Result, host string, rewrites []*LegacyRewrite, qtype uint16) { + for _, rw := range rewrites { + if rw.Type == qtype && (qtype == dns.TypeA || qtype == dns.TypeAAAA) { + if rw.IP == (netip.Addr{}) { + // "A"/"AAAA" exception: allow getting from upstream. + res.Reason = NotFilteredNotFound + + return + } + + res.IPList = append(res.IPList, rw.IP) + + log.Debug("rewrite: a/aaaa for %s is %s", host, rw.IP) + } + } +} + +// cloneRewrites returns a deep copy of entries. +func cloneRewrites(entries []*LegacyRewrite) (clone []*LegacyRewrite) { + clone = make([]*LegacyRewrite, len(entries)) + for i, rw := range entries { + clone[i] = &LegacyRewrite{ + Domain: rw.Domain, + Answer: rw.Answer, + IP: rw.IP, + Type: rw.Type, + } + } + + return clone +} diff --git a/internal/filtering/rewrites_test.go b/internal/filtering/rewrites_test.go index 17caa167..7f80df09 100644 --- a/internal/filtering/rewrites_test.go +++ b/internal/filtering/rewrites_test.go @@ -2,6 +2,7 @@ package filtering import ( "net" + "net/netip" "testing" "github.com/miekg/dns" @@ -15,33 +16,43 @@ func TestRewrites(t *testing.T) { d, _ := newForTest(t, nil, nil) t.Cleanup(d.Close) - d.Rewrites = []*LegacyRewrite{{ + var ( + addr1v4 = netip.AddrFrom4([4]byte{1, 2, 3, 4}) + addr2v4 = netip.AddrFrom4([4]byte{1, 2, 3, 5}) + addr3v4 = netip.AddrFrom4([4]byte{1, 2, 3, 6}) + addr4v4 = netip.AddrFrom4([4]byte{1, 2, 3, 7}) + + addr1v6 = netip.MustParseAddr("1:2:3::4") + addr2v6 = netip.MustParseAddr("1234::5678") + ) + + d.conf.Rewrites = []*LegacyRewrite{{ // This one and below are about CNAME, A and AAAA. Domain: "somecname", Answer: "somehost.com", }, { Domain: "somehost.com", - Answer: "0.0.0.0", + Answer: netip.IPv4Unspecified().String(), }, { Domain: "host.com", - Answer: "1.2.3.4", + Answer: addr1v4.String(), }, { Domain: "host.com", - Answer: "1.2.3.5", + Answer: addr2v4.String(), }, { Domain: "host.com", - Answer: "1:2:3::4", + Answer: addr1v6.String(), }, { Domain: "www.host.com", Answer: "host.com", }, { // This one is a wildcard. Domain: "*.host.com", - Answer: "1.2.3.5", + Answer: addr2v4.String(), }, { // This one and below are about wildcard overriding. Domain: "a.host.com", - Answer: "1.2.3.4", + Answer: addr1v4.String(), }, { // This one is about CNAME and wildcard interacting. Domain: "*.host2.com", @@ -59,13 +70,13 @@ func TestRewrites(t *testing.T) { Answer: "x.host.com", }, { Domain: "*.hostboth.com", - Answer: "1.2.3.6", + Answer: addr3v4.String(), }, { Domain: "*.hostboth.com", - Answer: "1234::5678", + Answer: addr2v6.String(), }, { Domain: "BIGHOST.COM", - Answer: "1.2.3.7", + Answer: addr4v4.String(), }, { Domain: "*.issue4016.com", Answer: "sub.issue4016.com", @@ -77,7 +88,7 @@ func TestRewrites(t *testing.T) { name string host string wantCName string - wantIPs []net.IP + wantIPs []netip.Addr wantReason Reason dtyp uint16 }{{ @@ -91,63 +102,63 @@ func TestRewrites(t *testing.T) { name: "rewritten_a", host: "www.host.com", wantCName: "host.com", - wantIPs: []net.IP{{1, 2, 3, 4}, {1, 2, 3, 5}}, + wantIPs: []netip.Addr{addr1v4, addr2v4}, wantReason: Rewritten, dtyp: dns.TypeA, }, { name: "rewritten_aaaa", host: "www.host.com", wantCName: "host.com", - wantIPs: []net.IP{net.ParseIP("1:2:3::4")}, + wantIPs: []netip.Addr{addr1v6}, wantReason: Rewritten, dtyp: dns.TypeAAAA, }, { name: "wildcard_match", host: "abc.host.com", wantCName: "", - wantIPs: []net.IP{{1, 2, 3, 5}}, + wantIPs: []netip.Addr{addr2v4}, wantReason: Rewritten, dtyp: dns.TypeA, }, { name: "wildcard_override", host: "a.host.com", wantCName: "", - wantIPs: []net.IP{{1, 2, 3, 4}}, + wantIPs: []netip.Addr{addr1v4}, wantReason: Rewritten, dtyp: dns.TypeA, }, { name: "wildcard_cname_interaction", host: "www.host2.com", wantCName: "host.com", - wantIPs: []net.IP{{1, 2, 3, 4}, {1, 2, 3, 5}}, + wantIPs: []netip.Addr{addr1v4, addr2v4}, wantReason: Rewritten, dtyp: dns.TypeA, }, { name: "two_cnames", host: "b.host.com", wantCName: "somehost.com", - wantIPs: []net.IP{{0, 0, 0, 0}}, + wantIPs: []netip.Addr{netip.IPv4Unspecified()}, wantReason: Rewritten, dtyp: dns.TypeA, }, { name: "two_cnames_and_wildcard", host: "b.host3.com", wantCName: "x.host.com", - wantIPs: []net.IP{{1, 2, 3, 5}}, + wantIPs: []netip.Addr{addr2v4}, wantReason: Rewritten, dtyp: dns.TypeA, }, { name: "issue3343", host: "www.hostboth.com", wantCName: "", - wantIPs: []net.IP{net.ParseIP("1234::5678")}, + wantIPs: []netip.Addr{addr2v6}, wantReason: Rewritten, dtyp: dns.TypeAAAA, }, { name: "issue3351", host: "bighost.com", wantCName: "", - wantIPs: []net.IP{{1, 2, 3, 7}}, + wantIPs: []netip.Addr{addr4v4}, wantReason: Rewritten, dtyp: dns.TypeA, }, { @@ -191,7 +202,7 @@ func TestRewritesLevels(t *testing.T) { d, _ := newForTest(t, nil, nil) t.Cleanup(d.Close) // Exact host, wildcard L2, wildcard L3. - d.Rewrites = []*LegacyRewrite{{ + d.conf.Rewrites = []*LegacyRewrite{{ Domain: "host.com", Answer: "1.1.1.1", Type: dns.TypeA, @@ -238,7 +249,7 @@ func TestRewritesExceptionCNAME(t *testing.T) { d, _ := newForTest(t, nil, nil) t.Cleanup(d.Close) // Wildcard and exception for a sub-domain. - d.Rewrites = []*LegacyRewrite{{ + d.conf.Rewrites = []*LegacyRewrite{{ Domain: "*.host.com", Answer: "2.2.2.2", }, { @@ -254,25 +265,25 @@ func TestRewritesExceptionCNAME(t *testing.T) { testCases := []struct { name string host string - want net.IP + want netip.Addr }{{ name: "match_subdomain", host: "my.host.com", - want: net.IP{2, 2, 2, 2}, + want: netip.AddrFrom4([4]byte{2, 2, 2, 2}), }, { name: "exception_cname", host: "sub.host.com", - want: nil, + want: netip.Addr{}, }, { name: "exception_wildcard", host: "my.sub.host.com", - want: nil, + want: netip.Addr{}, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { r := d.processRewrites(tc.host, dns.TypeA) - if tc.want == nil { + if tc.want == (netip.Addr{}) { assert.Equal(t, NotFilteredNotFound, r.Reason, "got %s", r.Reason) return @@ -280,7 +291,7 @@ func TestRewritesExceptionCNAME(t *testing.T) { assert.Equal(t, Rewritten, r.Reason) require.Len(t, r.IPList, 1) - assert.True(t, tc.want.Equal(r.IPList[0])) + assert.Equal(t, tc.want, r.IPList[0]) }) } } @@ -289,7 +300,7 @@ func TestRewritesExceptionIP(t *testing.T) { d, _ := newForTest(t, nil, nil) t.Cleanup(d.Close) // Exception for AAAA record. - d.Rewrites = []*LegacyRewrite{{ + d.conf.Rewrites = []*LegacyRewrite{{ Domain: "host.com", Answer: "1.2.3.4", Type: dns.TypeA, @@ -314,58 +325,58 @@ func TestRewritesExceptionIP(t *testing.T) { require.NoError(t, d.prepareRewrites()) testCases := []struct { - name string - host string - want []net.IP - dtyp uint16 + name string + host string + want []netip.Addr + dtyp uint16 + wantReason Reason }{{ - name: "match_A", - host: "host.com", - want: []net.IP{{1, 2, 3, 4}}, - dtyp: dns.TypeA, + name: "match_A", + host: "host.com", + want: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, + dtyp: dns.TypeA, + wantReason: Rewritten, }, { - name: "exception_AAAA_host.com", - host: "host.com", - want: nil, - dtyp: dns.TypeAAAA, + name: "exception_AAAA_host.com", + host: "host.com", + want: nil, + dtyp: dns.TypeAAAA, + wantReason: NotFilteredNotFound, }, { - name: "exception_A_host2.com", - host: "host2.com", - want: nil, - dtyp: dns.TypeA, + name: "exception_A_host2.com", + host: "host2.com", + want: nil, + dtyp: dns.TypeA, + wantReason: NotFilteredNotFound, }, { - name: "match_AAAA_host2.com", - host: "host2.com", - want: []net.IP{net.ParseIP("::1")}, - dtyp: dns.TypeAAAA, + name: "match_AAAA_host2.com", + host: "host2.com", + want: []netip.Addr{netip.MustParseAddr("::1")}, + dtyp: dns.TypeAAAA, + wantReason: Rewritten, }, { - name: "exception_A_host3.com", - host: "host3.com", - want: nil, - dtyp: dns.TypeA, + name: "exception_A_host3.com", + host: "host3.com", + want: nil, + dtyp: dns.TypeA, + wantReason: NotFilteredNotFound, }, { - name: "match_AAAA_host3.com", - host: "host3.com", - want: []net.IP{}, - dtyp: dns.TypeAAAA, + name: "match_AAAA_host3.com", + host: "host3.com", + want: nil, + dtyp: dns.TypeAAAA, + wantReason: Rewritten, }} for _, tc := range testCases { - t.Run(tc.name+"_"+tc.host, func(t *testing.T) { + t.Run(tc.name, func(t *testing.T) { + if tc.name != "match_AAAA_host3.com" { + t.SkipNow() + } + r := d.processRewrites(tc.host, tc.dtyp) - if tc.want == nil { - assert.Equal(t, NotFilteredNotFound, r.Reason) - - return - } - - assert.Equalf(t, Rewritten, r.Reason, "got %s", r.Reason) - - require.Len(t, r.IPList, len(tc.want)) - - for _, ip := range tc.want { - assert.True(t, ip.Equal(r.IPList[0])) - } + assert.Equal(t, tc.want, r.IPList) + assert.Equal(t, tc.wantReason, r.Reason) }) } } diff --git a/internal/filtering/safesearch/safesearch.go b/internal/filtering/safesearch/safesearch.go index 9d5b5121..f2d2b70c 100644 --- a/internal/filtering/safesearch/safesearch.go +++ b/internal/filtering/safesearch/safesearch.go @@ -8,6 +8,7 @@ import ( "encoding/gob" "fmt" "net" + "net/netip" "strings" "sync" "time" @@ -239,10 +240,9 @@ func (ss *Default) newResult( } if rewrite.RRType == qtype { - v := rewrite.Value - ip, ok := v.(net.IP) - if !ok || ip == nil { - return nil, fmt.Errorf("expected ip rewrite value, got %T(%[1]v)", v) + ip, ok := rewrite.Value.(netip.Addr) + if !ok || ip == (netip.Addr{}) { + return nil, fmt.Errorf("expected ip rewrite value, got %T(%[1]v)", rewrite.Value) } res.Rules[0].IP = ip @@ -267,12 +267,13 @@ func (ss *Default) newResult( for _, ip := range ips { // TODO(a.garipov): Remove this filtering once the resolver we use // actually learns about network. - ip = fitToProto(ip, qtype) - if ip == nil { + addr := fitToProto(ip, qtype) + if addr == (netip.Addr{}) { continue } - res.Rules[0].IP = ip + // TODO(e.burkov): Rules[0]? + res.Rules[0].IP = addr } return res, nil @@ -293,17 +294,16 @@ func qtypeToProto(qtype rules.RRType) (proto string) { // fitToProto returns a non-nil IP address if ip is the correct protocol version // for qtype. qtype is expected to be either [dns.TypeA] or [dns.TypeAAAA]. -func fitToProto(ip net.IP, qtype rules.RRType) (res net.IP) { - ip4 := ip.To4() - if qtype == dns.TypeA { - return ip4 +func fitToProto(ip net.IP, qtype rules.RRType) (res netip.Addr) { + if ip4 := ip.To4(); qtype == dns.TypeA { + if ip4 != nil { + return netip.AddrFrom4([4]byte(ip4)) + } + } else if ip = ip.To16(); ip != nil && qtype == dns.TypeAAAA { + return netip.AddrFrom16([16]byte(ip)) } - if ip4 == nil { - return ip - } - - return nil + return netip.Addr{} } // setCacheResult stores data in cache for host. qtype is expected to be either diff --git a/internal/filtering/safesearch/safesearch_internal_test.go b/internal/filtering/safesearch/safesearch_internal_test.go index 909265ee..ae4e380d 100644 --- a/internal/filtering/safesearch/safesearch_internal_test.go +++ b/internal/filtering/safesearch/safesearch_internal_test.go @@ -3,6 +3,7 @@ package safesearch import ( "context" "net" + "net/netip" "testing" "time" @@ -33,7 +34,7 @@ var defaultSafeSearchConf = filtering.SafeSearchConfig{ YouTube: true, } -var yandexIP = net.IPv4(213, 180, 193, 56) +var yandexIP = netip.AddrFrom4([4]byte{213, 180, 193, 56}) func newForTest(t testing.TB, ssConf filtering.SafeSearchConfig) (ss *Default) { ss, err := NewDefault(ssConf, "", testCacheSize, testCacheTTL) @@ -93,7 +94,7 @@ func TestSafeSearchCacheGoogle(t *testing.T) { OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) { ip4, ip6 := aghtest.HostToIPs(host) - return []net.IP{ip4, ip6}, nil + return []net.IP{ip4.AsSlice(), ip6.AsSlice()}, nil }, } @@ -109,14 +110,14 @@ func TestSafeSearchCacheGoogle(t *testing.T) { require.NoError(t, err) require.Len(t, res.Rules, 1) - assert.True(t, res.Rules[0].IP.Equal(wantIP)) + assert.Equal(t, wantIP, res.Rules[0].IP) // Check cache. cachedValue, isFound := ss.getCachedResult(domain, testQType) require.True(t, isFound) require.Len(t, cachedValue.Rules, 1) - assert.True(t, cachedValue.Rules[0].IP.Equal(wantIP)) + assert.Equal(t, wantIP, cachedValue.Rules[0].IP) } const googleHost = "www.google.com" diff --git a/internal/filtering/safesearch/safesearch_test.go b/internal/filtering/safesearch/safesearch_test.go index c62dd6e4..16b720d1 100644 --- a/internal/filtering/safesearch/safesearch_test.go +++ b/internal/filtering/safesearch/safesearch_test.go @@ -3,6 +3,7 @@ package safesearch_test import ( "context" "net" + "net/netip" "testing" "time" @@ -43,7 +44,7 @@ var testConf = filtering.SafeSearchConfig{ // yandexIP is the expected IP address of Yandex safe search results. Keep in // sync with the rules data. -var yandexIP = net.IPv4(213, 180, 193, 56) +var yandexIP = netip.AddrFrom4([4]byte{213, 180, 193, 56}) func TestDefault_CheckHost_yandex(t *testing.T) { conf := testConf @@ -87,7 +88,7 @@ func TestDefault_CheckHost_yandexAAAA(t *testing.T) { // once the TODO in [safesearch.Default.newResult] is resolved. require.Len(t, res.Rules, 1) - assert.Nil(t, res.Rules[0].IP) + assert.Empty(t, res.Rules[0].IP) assert.EqualValues(t, filtering.SafeSearchListID, res.Rules[0].FilterListID) } @@ -96,7 +97,7 @@ func TestDefault_CheckHost_google(t *testing.T) { OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) { ip4, ip6 := aghtest.HostToIPs(host) - return []net.IP{ip4, ip6}, nil + return []net.IP{ip4.AsSlice(), ip6.AsSlice()}, nil }, } @@ -178,7 +179,7 @@ func TestDefault_CheckHost_duckduckgoAAAA(t *testing.T) { // once the TODO in [safesearch.Default.newResult] is resolved. require.Len(t, res.Rules, 1) - assert.Nil(t, res.Rules[0].IP) + assert.Empty(t, res.Rules[0].IP) assert.EqualValues(t, filtering.SafeSearchListID, res.Rules[0].FilterListID) } diff --git a/internal/filtering/safesearchhttp.go b/internal/filtering/safesearchhttp.go index 6048cfea..eb6fa401 100644 --- a/internal/filtering/safesearchhttp.go +++ b/internal/filtering/safesearchhttp.go @@ -12,8 +12,8 @@ import ( // // Deprecated: Use handleSafeSearchSettings. func (d *DNSFilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) { - setProtectedBool(&d.confLock, &d.Config.SafeSearchConf.Enabled, true) - d.Config.ConfigModified() + setProtectedBool(d.confMu, &d.conf.SafeSearchConf.Enabled, true) + d.conf.ConfigModified() } // handleSafeSearchDisable is the handler for POST /control/safesearch/disable @@ -21,8 +21,8 @@ func (d *DNSFilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Reques // // Deprecated: Use handleSafeSearchSettings. func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) { - setProtectedBool(&d.confLock, &d.Config.SafeSearchConf.Enabled, false) - d.Config.ConfigModified() + setProtectedBool(d.confMu, &d.conf.SafeSearchConf.Enabled, false) + d.conf.ConfigModified() } // handleSafeSearchStatus is the handler for GET /control/safesearch/status @@ -30,13 +30,13 @@ func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Reque func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { var resp SafeSearchConfig func() { - d.confLock.RLock() - defer d.confLock.RUnlock() + d.confMu.RLock() + defer d.confMu.RUnlock() - resp = d.Config.SafeSearchConf + resp = d.conf.SafeSearchConf }() - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // handleSafeSearchSettings is the handler for PUT /control/safesearch/settings @@ -59,13 +59,13 @@ func (d *DNSFilter) handleSafeSearchSettings(w http.ResponseWriter, r *http.Requ } func() { - d.confLock.Lock() - defer d.confLock.Unlock() + d.confMu.Lock() + defer d.confMu.Unlock() - d.Config.SafeSearchConf = conf + d.conf.SafeSearchConf = conf }() - d.Config.ConfigModified() + d.conf.ConfigModified() aghhttp.OK(w) } diff --git a/internal/filtering/servicelist.go b/internal/filtering/servicelist.go index 246b2bc3..e00ac31e 100644 --- a/internal/filtering/servicelist.go +++ b/internal/filtering/servicelist.go @@ -30,10 +30,13 @@ var blockedServices = []blockedService{{ }, { ID: "activision_blizzard", Name: "Activision Blizzard", - IconSVG: []byte(""), + IconSVG: []byte(""), Rules: []string{ "||activision.com^", "||activisionblizzard.com^", + "||callofduty.com^", + "||callofdutyleague.com^", + "||codmwest.com^", "||demonware.net^", }, }, { @@ -287,6 +290,53 @@ var blockedServices = []blockedService{{ "||bnet.163.com^", "||bnet.cn^", }, +}, { + ID: "betano", + Name: "Betano", + IconSVG: []byte(""), + Rules: []string{ + "||betano.bg^", + "||betano.ca^", + "||betano.com^", + "||betano.cz^", + "||betano.de^", + "||betano.ng^", + "||betano.pt^", + }, +}, { + ID: "betfair", + Name: "Betfair", + IconSVG: []byte(""), + Rules: []string{ + "||betfair.com.au^", + "||betfair.com^", + "||betfair.es^", + "||betfair.it^", + "||betfair.ro^", + "||betfair.se^", + }, +}, { + ID: "betway", + Name: "Betway", + IconSVG: []byte(""), + Rules: []string{ + "||betway.be^", + "||betway.bet.ar^", + "||betway.co.za^", + "||betway.com.gh^", + "||betway.com.ng^", + "||betway.com^", + "||betway.de^", + "||betway.es^", + "||betway.fr^", + "||betway.it^", + "||betway.mx^", + "||betway.pl^", + "||betway.se^", + "||betwaygroup.com^", + "||betwaysatta.com^", + "||vietnambetway88.com^", + }, }, { ID: "bilibili", Name: "Bilibili", @@ -336,6 +386,16 @@ var blockedServices = []blockedService{{ "||mincdn.com^", "||yo9.com^", }, +}, { + ID: "blaze", + Name: "Blaze", + IconSVG: []byte(""), + Rules: []string{ + "||blaze.bet^", + "||blaze.com.br^", + "||blaze.com^", + "||blazecareers.com^", + }, }, { ID: "blizzard_entertainment", Name: "Blizzard Entertainment", @@ -344,12 +404,12 @@ var blockedServices = []blockedService{{ "||battle.net^", "||battlenet.com.cn^", "||blizzard.cn^", + "||blizzard.com^", "||blizzardgames.cn^", "||blz-contentstack.com^", "||blzstatic.cn^", "||bnet.163.com^", "||bnet.cn^", - "||lizzard.com^", }, }, { ID: "claro", @@ -855,6 +915,29 @@ var blockedServices = []blockedService{{ "||easyanticheat.net^", "||epicgames.com^", }, +}, { + ID: "espn", + Name: "ESPN", + IconSVG: []byte(""), + Rules: []string{ + "||es.pn^", + "||espn.cl^", + "||espn.co.uk^", + "||espn.com.ar^", + "||espn.com.au^", + "||espn.com.co^", + "||espn.com.ec^", + "||espn.com.mx^", + "||espn.com.pa^", + "||espn.com.pe^", + "||espn.com.uy^", + "||espn.com.ve^", + "||espn.com^", + "||espn.in", + "||espn.net^", + "||espncdn.com^", + "||espncricinfo.com^", + }, }, { ID: "facebook", Name: "Facebook", @@ -1304,6 +1387,14 @@ var blockedServices = []blockedService{{ "||zuckerberg.com^", "||zuckerberg.net^", }, +}, { + ID: "fifa", + Name: "FIFA", + IconSVG: []byte(""), + Rules: []string{ + "||fifa.com^", + "||fifaplus.com^", + }, }, { ID: "flickr", Name: "Flickr", @@ -1360,6 +1451,23 @@ var blockedServices = []blockedService{{ "||mask-h2.icloud.com^$dnsrewrite=NXDOMAIN;;", "||mask.icloud.com^$dnsrewrite=NXDOMAIN;;", }, +}, { + ID: "iheartradio", + Name: "iHeartRadio", + IconSVG: []byte(""), + Rules: []string{ + "||937theriver.com^", + "||iheart.com^", + "||iheart.mx^", + "||iheartmedia.com^", + "||iheartradio.ca^", + "||iheartradio.co.nz^", + "||iheartradio.com^", + "||ihrdev.com^", + "||ihrhls.com^", + "||ihrint.com^", + "||ihrstage.com^", + }, }, { ID: "imgur", Name: "Imgur", @@ -1561,7 +1669,6 @@ var blockedServices = []blockedService{{ "||cyberplace.social^", "||defcon.social^", "||det.social^", - "||fosstodon.org^", "||glasgow.social^", "||h4.io^", "||hachyderm.io^", @@ -1597,6 +1704,7 @@ var blockedServices = []blockedService{{ "||mastodon.sdf.org^", "||mastodon.social^", "||mastodon.social^", + "||mastodon.top^", "||mastodon.uno^", "||mastodon.world^", "||mastodon.zaclys.com^", @@ -1605,6 +1713,7 @@ var blockedServices = []blockedService{{ "||mastodont.cat^", "||mastodontech.de^", "||mastodontti.fi^", + "||mastouille.fr^", "||mathstodon.xyz^", "||metalhead.club^", "||mindly.social^", @@ -1632,7 +1741,6 @@ var blockedServices = []blockedService{{ "||social.anoxinon.de^", "||social.cologne^", "||social.dev-wiki.de^", - "||social.linux.pizza^", "||social.politicaconciencia.org^", "||social.vivaldi.net^", "||stranger.social^", @@ -1654,6 +1762,32 @@ var blockedServices = []blockedService{{ "||wien.rocks^", "||wxw.moe^", }, +}, { + ID: "mercado_libre", + Name: "Mercado Libre", + IconSVG: []byte(""), + Rules: []string{ + "||mercadolibre.cl^", + "||mercadolibre.co.cr^", + "||mercadolibre.com.ar^", + "||mercadolibre.com.bo^", + "||mercadolibre.com.co^", + "||mercadolibre.com.do^", + "||mercadolibre.com.ec^", + "||mercadolibre.com.gt^", + "||mercadolibre.com.hn^", + "||mercadolibre.com.mx^", + "||mercadolibre.com.ni^", + "||mercadolibre.com.pa^", + "||mercadolibre.com.pe^", + "||mercadolibre.com.py^", + "||mercadolibre.com.sv^", + "||mercadolibre.com.uy^", + "||mercadolibre.com.ve^", + "||mercadolibre.com^", + "||mercadolivre.com.br^", + "||mlstatic.com^", + }, }, { ID: "minecraft", Name: "Minecraft", @@ -1764,6 +1898,14 @@ var blockedServices = []blockedService{{ "||origin.tv^", "||signin.ea.com^", }, +}, { + ID: "paramountplus", + Name: "Paramount Plus", + IconSVG: []byte(""), + Rules: []string{ + "||paramountplus.com^", + "||pplusstatic.com^", + }, }, { ID: "pinterest", Name: "Pinterest", @@ -1833,6 +1975,13 @@ var blockedServices = []blockedService{{ "||sonyentertainmentnetwork.com", "||station.sony.com", }, +}, { + ID: "pluto_tv", + Name: "Pluto TV", + IconSVG: []byte(""), + Rules: []string{ + "||pluto.tv^", + }, }, { ID: "qq", Name: "QQ", @@ -1879,8 +2028,14 @@ var blockedServices = []blockedService{{ Name: "Roblox", IconSVG: []byte(""), Rules: []string{ + "||blox.com^", + "||rbx.cn^", + "||rbx.com^", "||rbxadder.com^", "||rbxcdn.com^", + "||rbxcdn.net^", + "||rbxinfra.com^", + "||rbxinfra.net^", "||roblox.cn^", "||roblox.com^", "||roblox.qq.com^", @@ -1889,12 +2044,22 @@ var blockedServices = []blockedService{{ }, }, { ID: "rockstar_games", - Name: "Rockstar games", + Name: "Rockstar Games", IconSVG: []byte(""), Rules: []string{ "||rockstargames.com^", "||rsg.sc^", }, +}, { + ID: "shein", + Name: "Shein", + IconSVG: []byte(""), + Rules: []string{ + "||shein.co.uk^", + "||shein.com^", + "||shein.se^", + "||sheinsz.ltwebstatic.com^", + }, }, { ID: "shopee", Name: "Shopee", @@ -2105,8 +2270,8 @@ var blockedServices = []blockedService{{ }, }, { ID: "twitter", - Name: "Twitter", - IconSVG: []byte(""), + Name: "X (formerly Twitter)", + IconSVG: []byte(""), Rules: []string{ "||ads-twitter.com^", "||cms-twdigitalassets.com^", @@ -2130,6 +2295,7 @@ var blockedServices = []blockedService{{ "||twttr.net^", "||twvid.com^", "||vine.co^", + "||x.com^", }, }, { ID: "ubisoft", diff --git a/internal/home/client.go b/internal/home/client.go index c0d39dad..751bde6d 100644 --- a/internal/home/client.go +++ b/internal/home/client.go @@ -1,10 +1,10 @@ package home import ( - "encoding" "fmt" "time" + "github.com/AdguardTeam/AdGuardHome/internal/client" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch" "github.com/AdguardTeam/AdGuardHome/internal/whois" @@ -83,50 +83,6 @@ func (c *Client) setSafeSearch( 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 { @@ -138,5 +94,5 @@ type RuntimeClient struct { // Source is the source from which the information about the client has // been obtained. - Source clientSource + Source client.Source } diff --git a/internal/home/clients.go b/internal/home/clients.go index 049710bc..68e0b7d1 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -10,8 +10,8 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" + "github.com/AdguardTeam/AdGuardHome/internal/arpdb" "github.com/AdguardTeam/AdGuardHome/internal/client" - "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/filtering" @@ -34,7 +34,7 @@ type DHCP interface { // HostByIP returns the hostname of the DHCP client with the given IP // address. The address will be netip.Addr{} if there is no such client, - // due to an assumption that a DHCP client must always have an IP address. + // due to an assumption that a DHCP client must always have a hostname. HostByIP(ip netip.Addr) (host string) // MACByIP returns the MAC address for the given IP address leased. It @@ -55,8 +55,8 @@ type clientsContainer struct { allTags *stringutil.Set - // dhcpServer is used for looking up clients IP addresses by MAC addresses - dhcpServer dhcpd.Interface + // dhcp is the DHCP service implementation. + dhcp DHCP // dnsServer is used for checking clients IP status access list status dnsServer *dnsforward.Server @@ -65,8 +65,8 @@ type clientsContainer struct { // hosts database. etcHosts *aghnet.HostsContainer - // arpdb stores the neighbors retrieved from ARP. - arpdb aghnet.ARPDB + // arpDB stores the neighbors retrieved from ARP. + arpDB arpdb.Interface // lock protects all fields. // @@ -93,9 +93,9 @@ type clientsContainer struct { // Note: this function must be called only once func (clients *clientsContainer) Init( objects []*clientObject, - dhcpServer dhcpd.Interface, + dhcpServer DHCP, etcHosts *aghnet.HostsContainer, - arpdb aghnet.ARPDB, + arpDB arpdb.Interface, filteringConf *filtering.Config, ) (err error) { if clients.list != nil { @@ -108,9 +108,11 @@ func (clients *clientsContainer) Init( clients.allTags = stringutil.NewSet(clientTags...) - clients.dhcpServer = dhcpServer + // TODO(e.burkov): Use [dhcpsvc] implementation when it's ready. + clients.dhcp = dhcpServer + clients.etcHosts = etcHosts - clients.arpdb = arpdb + clients.arpDB = arpDB err = clients.addFromConfig(objects, filteringConf) if err != nil { // Don't wrap the error, because it's informative enough as is. @@ -124,11 +126,6 @@ func (clients *clientsContainer) Init( return nil } - if clients.dhcpServer != nil { - clients.dhcpServer.SetOnLeaseChanged(clients.onDHCPLeaseChanged) - clients.onDHCPLeaseChanged(dhcpd.LeaseChangedAdded) - } - if clients.etcHosts != nil { go clients.handleHostsUpdates() } @@ -164,7 +161,7 @@ func (clients *clientsContainer) Start() { // reloadARP reloads runtime clients from ARP, if configured. func (clients *clientsContainer) reloadARP() { - if clients.arpdb != nil { + if clients.arpDB != nil { clients.addFromSystemARP() } } @@ -290,8 +287,8 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) { // above loop can generate different orderings when writing to the config // file: this produces lots of diffs in config files, so sort objects by // name before writing. - slices.SortStableFunc(objs, func(a, b *clientObject) (sortsBefore bool) { - return a.Name < b.Name + slices.SortStableFunc(objs, func(a, b *clientObject) (res int) { + return strings.Compare(a.Name, b.Name) }) return objs @@ -309,58 +306,28 @@ func (clients *clientsContainer) periodicUpdate() { } } -// onDHCPLeaseChanged is a callback for the DHCP server. It updates the list of -// runtime clients using the DHCP server's leases. -// -// TODO(e.burkov): Remove when switched to dhcpsvc. -func (clients *clientsContainer) onDHCPLeaseChanged(flags int) { - if clients.dhcpServer == nil || !config.Clients.Sources.DHCP { - return - } - - clients.lock.Lock() - defer clients.lock.Unlock() - - clients.rmHostsBySrc(ClientSourceDHCP) - - if flags == dhcpd.LeaseChangedRemovedAll { - return - } - - leases := clients.dhcpServer.Leases(dhcpd.LeasesAll) - n := 0 - for _, l := range leases { - if l.Hostname == "" { - continue - } - - ok := clients.addHostLocked(l.IP, l.Hostname, ClientSourceDHCP) - if ok { - n++ - } - } - - log.Debug("clients: added %d client aliases from dhcp", n) -} - // clientSource checks if client with this IP address already exists and returns -// the source which updated it last. It returns [ClientSourceNone] if the +// the source which updated it last. It returns [client.SourceNone] if the // client doesn't exist. -func (clients *clientsContainer) clientSource(ip netip.Addr) (src clientSource) { +func (clients *clientsContainer) clientSource(ip netip.Addr) (src client.Source) { clients.lock.Lock() defer clients.lock.Unlock() _, ok := clients.findLocked(ip.String()) if ok { - return ClientSourcePersistent + return client.SourcePersistent } rc, ok := clients.ipToRC[ip] if ok { - return rc.Source + src = rc.Source } - return ClientSourceNone + if src < client.SourceDHCP && clients.dhcp.HostByIP(ip) != "" { + src = client.SourceDHCP + } + + return src } // findMultiple is a wrapper around Find to make it a valid client finder for @@ -521,17 +488,14 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) { } } - if clients.dhcpServer != nil { - return clients.findDHCP(ip) - } - - return nil, false + // TODO(e.burkov): Iterate through clients.list only once. + 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) + foundMAC := clients.dhcp.MACByIP(ip) if foundMAC == nil { return nil, false } @@ -552,8 +516,9 @@ func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *Client, ok bool) { return nil, false } -// findRuntimeClient finds a runtime client by their IP. -func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) { +// runtimeClient returns a runtime client from internal index. Note that it +// doesn't include DHCP clients. +func (clients *clientsContainer) runtimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) { if ip == (netip.Addr{}) { return nil, false } @@ -566,6 +531,24 @@ func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeCl return rc, ok } +// findRuntimeClient finds a runtime client by their IP. +func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) { + if rc, ok = clients.runtimeClient(ip); ok && rc.Source > client.SourceDHCP { + return rc, ok + } + + host := clients.dhcp.HostByIP(ip) + if host == "" { + return rc, ok + } + + return &RuntimeClient{ + Host: host, + Source: client.SourceDHCP, + WHOIS: &whois.Info{}, + }, true +} + // check validates the client. func (clients *clientsContainer) check(c *Client) (err error) { switch { @@ -761,7 +744,7 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) { // Create a RuntimeClient implicitly so that we don't do this check // again. rc = &RuntimeClient{ - Source: ClientSourceWHOIS, + Source: client.SourceWHOIS, } clients.ipToRC[ip] = rc @@ -780,7 +763,7 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) { func (clients *clientsContainer) addHost( ip netip.Addr, host string, - src clientSource, + src client.Source, ) (ok bool) { clients.lock.Lock() defer clients.lock.Unlock() @@ -803,7 +786,7 @@ func (clients *clientsContainer) UpdateAddress(ip netip.Addr, host string, info defer clients.lock.Unlock() if host != "" { - ok := clients.addHostLocked(ip, host, ClientSourceRDNS) + ok := clients.addHostLocked(ip, host, client.SourceRDNS) if !ok { log.Debug("clients: host for client %q already set with higher priority source", ip) } @@ -819,14 +802,19 @@ func (clients *clientsContainer) UpdateAddress(ip netip.Addr, host string, info func (clients *clientsContainer) addHostLocked( ip netip.Addr, host string, - src clientSource, + src client.Source, ) (ok bool) { rc, ok := clients.ipToRC[ip] if !ok { + if src < client.SourceDHCP { + if clients.dhcp.HostByIP(ip) != "" { + return false + } + } + rc = &RuntimeClient{ WHOIS: &whois.Info{}, } - clients.ipToRC[ip] = rc } else if src < rc.Source { return false @@ -841,7 +829,7 @@ func (clients *clientsContainer) addHostLocked( } // rmHostsBySrc removes all entries that match the specified source. -func (clients *clientsContainer) rmHostsBySrc(src clientSource) { +func (clients *clientsContainer) rmHostsBySrc(src client.Source) { n := 0 for ip, rc := range clients.ipToRC { if rc.Source == src { @@ -855,15 +843,19 @@ func (clients *clientsContainer) rmHostsBySrc(src clientSource) { // addFromHostsFile fills the client-hostname pairing index from the system's // hosts files. -func (clients *clientsContainer) addFromHostsFile(hosts aghnet.HostsRecords) { +func (clients *clientsContainer) addFromHostsFile(hosts aghnet.Hosts) { clients.lock.Lock() defer clients.lock.Unlock() - clients.rmHostsBySrc(ClientSourceHostsFile) + clients.rmHostsBySrc(client.SourceHostsFile) n := 0 - for ip, rec := range hosts { - clients.addHostLocked(ip, rec.Canonical, ClientSourceHostsFile) + for addr, rec := range hosts { + // Only the first name of the first record is considered a canonical + // hostname for the IP address. + // + // TODO(e.burkov): Consider using all the names from all the records. + clients.addHostLocked(addr, rec[0].Names[0], client.SourceHostsFile) n++ } @@ -873,15 +865,15 @@ func (clients *clientsContainer) addFromHostsFile(hosts aghnet.HostsRecords) { // addFromSystemARP adds the IP-hostname pairings from the output of the arp -a // command. func (clients *clientsContainer) addFromSystemARP() { - if err := clients.arpdb.Refresh(); err != nil { + if err := clients.arpDB.Refresh(); err != nil { log.Error("refreshing arp container: %s", err) - clients.arpdb = aghnet.EmptyARPDB{} + clients.arpDB = arpdb.Empty{} return } - ns := clients.arpdb.Neighbors() + ns := clients.arpDB.Neighbors() if len(ns) == 0 { log.Debug("refreshing arp container: the update is empty") @@ -891,11 +883,11 @@ func (clients *clientsContainer) addFromSystemARP() { clients.lock.Lock() defer clients.lock.Unlock() - clients.rmHostsBySrc(ClientSourceARP) + clients.rmHostsBySrc(client.SourceARP) added := 0 for _, n := range ns { - if clients.addHostLocked(n.IP, n.Name, ClientSourceARP) { + if clients.addHostLocked(n.IP, n.Name, client.SourceARP) { added++ } } @@ -907,7 +899,9 @@ func (clients *clientsContainer) addFromSystemARP() { // the persistent clients. func (clients *clientsContainer) close() (err error) { persistent := maps.Values(clients.list) - slices.SortFunc(persistent, func(a, b *Client) (less bool) { return a.Name < b.Name }) + slices.SortFunc(persistent, func(a, b *Client) (res int) { + return strings.Compare(a.Name, b.Name) + }) var errs []error @@ -917,9 +911,5 @@ func (clients *clientsContainer) close() (err error) { } } - if len(errs) > 0 { - return errors.List("closing client specific upstreams", errs...) - } - - return nil + return errors.Join(errs...) } diff --git a/internal/home/clients_internal_test.go b/internal/home/clients_internal_test.go index d3ff2a57..b8ef598f 100644 --- a/internal/home/clients_internal_test.go +++ b/internal/home/clients_internal_test.go @@ -7,22 +7,46 @@ import ( "testing" "time" + "github.com/AdguardTeam/AdGuardHome/internal/client" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" + "github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/whois" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +type testDHCP struct { + OnLeases func() (leases []*dhcpsvc.Lease) + OnHostBy func(ip netip.Addr) (host string) + OnMACBy func(ip netip.Addr) (mac net.HardwareAddr) +} + +// Lease implements the [DHCP] interface for testDHCP. +func (t *testDHCP) Leases() (leases []*dhcpsvc.Lease) { return t.OnLeases() } + +// HostByIP implements the [DHCP] interface for testDHCP. +func (t *testDHCP) HostByIP(ip netip.Addr) (host string) { return t.OnHostBy(ip) } + +// MACByIP implements the [DHCP] interface for testDHCP. +func (t *testDHCP) MACByIP(ip netip.Addr) (mac net.HardwareAddr) { return t.OnMACBy(ip) } + // newClientsContainer is a helper that creates a new clients container for // tests. func newClientsContainer(t *testing.T) (c *clientsContainer) { + t.Helper() + c = &clientsContainer{ testing: true, } - err := c.Init(nil, nil, nil, nil, &filtering.Config{}) - require.NoError(t, err) + dhcp := &testDHCP{ + OnLeases: func() (leases []*dhcpsvc.Lease) { panic("not implemented") }, + OnHostBy: func(ip netip.Addr) (host string) { return "" }, + OnMACBy: func(ip netip.Addr) (mac net.HardwareAddr) { return nil }, + } + + require.NoError(t, c.Init(nil, dhcp, nil, nil, &filtering.Config{})) return c } @@ -76,9 +100,9 @@ func TestClients(t *testing.T) { assert.Equal(t, "client2", c.Name) - assert.Equal(t, clients.clientSource(cliNoneIP), ClientSourceNone) - assert.Equal(t, clients.clientSource(cli1IP), ClientSourcePersistent) - assert.Equal(t, clients.clientSource(cli2IP), ClientSourcePersistent) + assert.Equal(t, clients.clientSource(cliNoneIP), client.SourceNone) + assert.Equal(t, clients.clientSource(cli1IP), client.SourcePersistent) + assert.Equal(t, clients.clientSource(cli2IP), client.SourcePersistent) }) t.Run("add_fail_name", func(t *testing.T) { @@ -125,8 +149,8 @@ func TestClients(t *testing.T) { }) require.NoError(t, err) - assert.Equal(t, clients.clientSource(cliOldIP), ClientSourceNone) - assert.Equal(t, clients.clientSource(cliNewIP), ClientSourcePersistent) + assert.Equal(t, clients.clientSource(cliOldIP), client.SourceNone) + assert.Equal(t, clients.clientSource(cliNewIP), client.SourcePersistent) prev, ok = clients.list["client1"] require.True(t, ok) @@ -158,7 +182,7 @@ func TestClients(t *testing.T) { ok := clients.Del("client1-renamed") require.True(t, ok) - assert.Equal(t, clients.clientSource(netip.MustParseAddr("1.1.1.2")), ClientSourceNone) + assert.Equal(t, clients.clientSource(netip.MustParseAddr("1.1.1.2")), client.SourceNone) }) t.Run("del_fail", func(t *testing.T) { @@ -168,32 +192,32 @@ func TestClients(t *testing.T) { t.Run("addhost_success", func(t *testing.T) { ip := netip.MustParseAddr("1.1.1.1") - ok := clients.addHost(ip, "host", ClientSourceARP) + ok := clients.addHost(ip, "host", client.SourceARP) assert.True(t, ok) - ok = clients.addHost(ip, "host2", ClientSourceARP) + ok = clients.addHost(ip, "host2", client.SourceARP) assert.True(t, ok) - ok = clients.addHost(ip, "host3", ClientSourceHostsFile) + ok = clients.addHost(ip, "host3", client.SourceHostsFile) assert.True(t, ok) - assert.Equal(t, clients.clientSource(ip), ClientSourceHostsFile) + assert.Equal(t, clients.clientSource(ip), client.SourceHostsFile) }) t.Run("dhcp_replaces_arp", func(t *testing.T) { ip := netip.MustParseAddr("1.2.3.4") - ok := clients.addHost(ip, "from_arp", ClientSourceARP) + ok := clients.addHost(ip, "from_arp", client.SourceARP) assert.True(t, ok) - assert.Equal(t, clients.clientSource(ip), ClientSourceARP) + assert.Equal(t, clients.clientSource(ip), client.SourceARP) - ok = clients.addHost(ip, "from_dhcp", ClientSourceDHCP) + ok = clients.addHost(ip, "from_dhcp", client.SourceDHCP) assert.True(t, ok) - assert.Equal(t, clients.clientSource(ip), ClientSourceDHCP) + assert.Equal(t, clients.clientSource(ip), client.SourceDHCP) }) t.Run("addhost_fail", func(t *testing.T) { ip := netip.MustParseAddr("1.1.1.1") - ok := clients.addHost(ip, "host1", ClientSourceRDNS) + ok := clients.addHost(ip, "host1", client.SourceRDNS) assert.False(t, ok) }) } @@ -216,7 +240,7 @@ func TestClientsWHOIS(t *testing.T) { t.Run("existing_auto-client", func(t *testing.T) { ip := netip.MustParseAddr("1.1.1.1") - ok := clients.addHost(ip, "host", ClientSourceRDNS) + ok := clients.addHost(ip, "host", client.SourceRDNS) assert.True(t, ok) clients.setWHOISInfo(ip, whois) @@ -259,7 +283,7 @@ func TestClientsAddExisting(t *testing.T) { assert.True(t, ok) // Now add an auto-client with the same IP. - ok = clients.addHost(ip, "test", ClientSourceRDNS) + ok = clients.addHost(ip, "test", client.SourceRDNS) assert.True(t, ok) }) @@ -288,7 +312,7 @@ func TestClientsAddExisting(t *testing.T) { dhcpServer, err := dhcpd.Create(config) require.NoError(t, err) - clients.dhcpServer = dhcpServer + clients.dhcp = dhcpServer err = dhcpServer.AddStaticLease(&dhcpd.Lease{ HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go index 38ea666d..62a46e78 100644 --- a/internal/home/clientshttp.go +++ b/internal/home/clientshttp.go @@ -8,6 +8,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" + "github.com/AdguardTeam/AdGuardHome/internal/client" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/schedule" "github.com/AdguardTeam/AdGuardHome/internal/whois" @@ -34,8 +35,12 @@ type clientJSON struct { WHOIS *whois.Info `json:"whois_info,omitempty"` SafeSearchConf *filtering.SafeSearchConfig `json:"safe_search"` + // Schedule is blocked services schedule for every day of the week. + Schedule *schedule.Weekly `json:"blocked_services_schedule"` + Name string `json:"name"` + // BlockedServices is the names of blocked services. BlockedServices []string `json:"blocked_services"` IDs []string `json:"ids"` Tags []string `json:"tags"` @@ -53,12 +58,40 @@ type clientJSON struct { IgnoreStatistics aghalg.NullBool `json:"ignore_statistics"` } +// copySettings returns a copy of specific settings from JSON or a previous +// client. +func (j *clientJSON) copySettings( + prev *Client, +) (weekly *schedule.Weekly, ignoreQueryLog, ignoreStatistics bool) { + if j.Schedule != nil { + weekly = j.Schedule.Clone() + } else if prev != nil && prev.BlockedServices != nil { + weekly = prev.BlockedServices.Schedule.Clone() + } else { + weekly = schedule.EmptyWeekly() + } + + if j.IgnoreQueryLog != aghalg.NBNull { + ignoreQueryLog = j.IgnoreQueryLog == aghalg.NBTrue + } else if prev != nil { + ignoreQueryLog = prev.IgnoreQueryLog + } + + if j.IgnoreStatistics != aghalg.NBNull { + ignoreStatistics = j.IgnoreStatistics == aghalg.NBTrue + } else if prev != nil { + ignoreStatistics = prev.IgnoreStatistics + } + + return weekly, ignoreQueryLog, ignoreStatistics +} + type runtimeClientJSON struct { WHOIS *whois.Info `json:"whois_info"` - IP netip.Addr `json:"ip"` - Name string `json:"name"` - Source clientSource `json:"source"` + IP netip.Addr `json:"ip"` + Name string `json:"name"` + Source client.Source `json:"source"` } type clientListJSON struct { @@ -91,9 +124,20 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http data.RuntimeClients = append(data.RuntimeClients, cj) } + for _, l := range clients.dhcp.Leases() { + cj := runtimeClientJSON{ + Name: l.Hostname, + Source: client.SourceDHCP, + IP: l.IP, + WHOIS: &whois.Info{}, + } + + data.RuntimeClients = append(data.RuntimeClients, cj) + } + data.Tags = clientTags - _ = aghhttp.WriteJSONResponse(w, r, data) + aghhttp.WriteJSONResponseOK(w, r, data) } // jsonToClient converts JSON object to Client object. @@ -119,9 +163,15 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C } } - weekly := schedule.EmptyWeekly() - if prev != nil { - weekly = prev.BlockedServices.Schedule.Clone() + weekly, ignoreQueryLog, ignoreStatistics := cj.copySettings(prev) + + bs := &filtering.BlockedServices{ + Schedule: weekly, + IDs: cj.BlockedServices, + } + err = bs.Validate() + if err != nil { + return nil, fmt.Errorf("validating blocked services: %w", err) } c = &Client{ @@ -129,10 +179,7 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C Name: cj.Name, - BlockedServices: &filtering.BlockedServices{ - Schedule: weekly, - IDs: cj.BlockedServices, - }, + BlockedServices: bs, IDs: cj.IDs, Tags: cj.Tags, @@ -143,18 +190,8 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C ParentalEnabled: cj.ParentalEnabled, SafeBrowsingEnabled: cj.SafeBrowsingEnabled, UseOwnBlockedServices: !cj.UseGlobalBlockedServices, - } - - if cj.IgnoreQueryLog != aghalg.NBNull { - c.IgnoreQueryLog = cj.IgnoreQueryLog == aghalg.NBTrue - } else if prev != nil { - c.IgnoreQueryLog = prev.IgnoreQueryLog - } - - if cj.IgnoreStatistics != aghalg.NBNull { - c.IgnoreStatistics = cj.IgnoreStatistics == aghalg.NBTrue - } else if prev != nil { - c.IgnoreStatistics = prev.IgnoreStatistics + IgnoreQueryLog: ignoreQueryLog, + IgnoreStatistics: ignoreStatistics, } if safeSearchConf.Enabled { @@ -191,6 +228,7 @@ func clientToJSON(c *Client) (cj *clientJSON) { UseGlobalBlockedServices: !c.UseOwnBlockedServices, + Schedule: c.BlockedServices.Schedule, BlockedServices: c.BlockedServices.IDs, Upstreams: c.Upstreams, @@ -338,7 +376,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http }) } - _ = aghhttp.WriteJSONResponse(w, r, data) + aghhttp.WriteJSONResponseOK(w, r, data) } // findRuntime looks up the IP in runtime and temporary storages, like diff --git a/internal/home/config.go b/internal/home/config.go index 361fdebb..0be84753 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -10,6 +10,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghtls" + "github.com/AdguardTeam/AdGuardHome/internal/confmigrate" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/filtering" @@ -21,7 +22,6 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/timeutil" "github.com/google/renameio/v2/maybe" - "golang.org/x/exp/slices" yaml "gopkg.in/yaml.v3" ) @@ -114,8 +114,6 @@ type configuration struct { Language string `yaml:"language"` // Theme is a UI theme for current user. Theme Theme `yaml:"theme"` - // DebugPProf defines if the profiling HTTP handler will listen on :6060. - DebugPProf bool `yaml:"debug_pprof"` DNS dnsConfig `yaml:"dns"` TLS tlsConfigSettings `yaml:"tls"` @@ -133,7 +131,8 @@ type configuration struct { WhitelistFilters []filtering.FilterYAML `yaml:"whitelist_filters"` UserRules []string `yaml:"user_rules"` - DHCP *dhcpd.ServerConfig `yaml:"dhcp"` + DHCP *dhcpd.ServerConfig `yaml:"dhcp"` + Filtering *filtering.Config `yaml:"filtering"` // Clients contains the YAML representations of the persistent clients. // This field is only used for reading and writing persistent client data. @@ -147,7 +146,9 @@ type configuration struct { sync.RWMutex `yaml:"-"` - SchemaVersion int `yaml:"schema_version"` // keeping last so that users will be less tempted to change it -- used when upgrading between versions + // SchemaVersion is the version of the configuration schema. See + // [confmigrate.LastSchemaVersion]. + SchemaVersion uint `yaml:"schema_version"` } // httpConfig is a block with HTTP configuration params. @@ -155,6 +156,9 @@ type configuration struct { // Field ordering is important, YAML fields better not to be reordered, if it's // not absolutely necessary. type httpConfig struct { + // Pprof defines the profiling HTTP handler. + Pprof *httpPprofConfig `yaml:"pprof"` + // Address is the address to serve the web UI on. Address netip.AddrPort @@ -163,6 +167,15 @@ type httpConfig struct { SessionTTL timeutil.Duration `yaml:"session_ttl"` } +// httpPprofConfig is the block with pprof HTTP configuration. +type httpPprofConfig struct { + // Port for the profiling handler. + Port uint16 `yaml:"port"` + + // Enabled defines if the profiling handler is enabled. + Enabled bool `yaml:"enabled"` +} + // dnsConfig is a block with DNS configuration params. // // Field ordering is important, YAML fields better not to be reordered, if it's @@ -175,9 +188,10 @@ type dnsConfig struct { // in query log and statistics. AnonymizeClientIP bool `yaml:"anonymize_client_ip"` - dnsforward.FilteringConfig `yaml:",inline"` - - DnsfilterConf *filtering.Config `yaml:",inline"` + // Config is the embed configuration with DNS params. + // + // TODO(a.garipov): Remove embed. + dnsforward.Config `yaml:",inline"` // UpstreamTimeout is the timeout for querying upstream servers. UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"` @@ -277,18 +291,19 @@ var config = &configuration{ HTTPConfig: httpConfig{ Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000), SessionTTL: timeutil.Duration{Duration: 30 * timeutil.Day}, + Pprof: &httpPprofConfig{ + Enabled: false, + Port: 6060, + }, }, DNS: dnsConfig{ BindHosts: []netip.Addr{netip.IPv4Unspecified()}, Port: defaultPortDNS, - FilteringConfig: dnsforward.FilteringConfig{ - ProtectionEnabled: true, // whether or not use any of filtering features - BlockingMode: dnsforward.BlockingModeDefault, - BlockedResponseTTL: 10, // in seconds - Ratelimit: 20, - RefuseAny: true, - AllServers: false, - HandleDDR: true, + Config: dnsforward.Config{ + Ratelimit: 20, + RefuseAny: true, + AllServers: false, + HandleDDR: true, FastestTimeout: timeutil.Duration{ Duration: fastip.DefaultPingWaitTimeout, }, @@ -308,33 +323,6 @@ var config = &configuration{ // was later increased to 300 due to https://github.com/AdguardTeam/AdGuardHome/issues/2257 MaxGoroutines: 300, }, - DnsfilterConf: &filtering.Config{ - FilteringEnabled: true, - FiltersUpdateIntervalHours: 24, - - ParentalEnabled: false, - SafeBrowsingEnabled: false, - - SafeBrowsingCacheSize: 1 * 1024 * 1024, - SafeSearchCacheSize: 1 * 1024 * 1024, - ParentalCacheSize: 1 * 1024 * 1024, - CacheTime: 30, - - SafeSearchConf: filtering.SafeSearchConfig{ - Enabled: false, - Bing: true, - DuckDuckGo: true, - Google: true, - Pixabay: true, - Yandex: true, - YouTube: true, - }, - - BlockedServices: &filtering.BlockedServices{ - Schedule: schedule.EmptyWeekly(), - IDs: []string{}, - }, - }, UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout}, UsePrivateRDNS: true, }, @@ -371,6 +359,37 @@ var config = &configuration{ URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt", Name: "AdAway Default Blocklist", }}, + Filtering: &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + BlockedResponseTTL: 10, // in seconds + + FilteringEnabled: true, + FiltersUpdateIntervalHours: 24, + + ParentalEnabled: false, + SafeBrowsingEnabled: false, + + SafeBrowsingCacheSize: 1 * 1024 * 1024, + SafeSearchCacheSize: 1 * 1024 * 1024, + ParentalCacheSize: 1 * 1024 * 1024, + CacheTime: 30, + + SafeSearchConf: filtering.SafeSearchConfig{ + Enabled: false, + Bing: true, + DuckDuckGo: true, + Google: true, + Pixabay: true, + Yandex: true, + YouTube: true, + }, + + BlockedServices: &filtering.BlockedServices{ + Schedule: schedule.EmptyWeekly(), + IDs: []string{}, + }, + }, DHCP: &dhcpd.ServerConfig{ LocalDomainName: "lan", Conf4: dhcpd.V4ServerConf{ @@ -398,7 +417,7 @@ var config = &configuration{ MaxAge: 3, }, OSConfig: &osConfig{}, - SchemaVersion: currentSchemaVersion, + SchemaVersion: confmigrate.LastSchemaVersion, Theme: ThemeAuto, } @@ -414,28 +433,10 @@ func (c *configuration) getConfigFilename() string { if !filepath.IsAbs(configFile) { configFile = filepath.Join(Context.workDir, configFile) } + return configFile } -// readLogSettings reads logging settings from the config file. We do it in a -// separate method in order to configure logger before the actual configuration -// is parsed and applied. -func readLogSettings() (ls *logSettings) { - conf := &configuration{} - - yamlFile, err := readConfigFile() - if err != nil { - return &logSettings{} - } - - err = yaml.Unmarshal(yamlFile, conf) - if err != nil { - log.Error("Couldn't get logging settings from the configuration: %s", err) - } - - return &conf.Log -} - // validateBindHosts returns error if any of binding hosts from configuration is // not a valid IP address. func validateBindHosts(conf *configuration) (err error) { @@ -452,21 +453,59 @@ func validateBindHosts(conf *configuration) (err error) { return nil } -// parseConfig loads configuration from the YAML file +// parseConfig loads configuration from the YAML file, upgrading it if +// necessary. func parseConfig() (err error) { - var fileData []byte - fileData, err = readConfigFile() + // Do the upgrade if necessary. + config.fileData, err = readConfigFile() if err != nil { return err } - config.fileData = nil - err = yaml.Unmarshal(fileData, &config) + migrator := confmigrate.New(&confmigrate.Config{ + WorkingDir: Context.workDir, + }) + + var upgraded bool + config.fileData, upgraded, err = migrator.Migrate( + config.fileData, + confmigrate.LastSchemaVersion, + ) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return err + } else if upgraded { + err = maybe.WriteFile(config.getConfigFilename(), config.fileData, 0o644) + if err != nil { + return fmt.Errorf("writing new config: %w", err) + } + } + + err = yaml.Unmarshal(config.fileData, &config) if err != nil { // Don't wrap the error since it's informative enough as is. return err } + err = validateConfig() + if err != nil { + return err + } + + if config.DNS.UpstreamTimeout.Duration == 0 { + config.DNS.UpstreamTimeout = timeutil.Duration{Duration: dnsforward.DefaultTimeout} + } + + err = setContextTLSCipherIDs() + if err != nil { + return err + } + + return nil +} + +// validateConfig returns error if the configuration is invalid. +func validateConfig() (err error) { err = validateBindHosts(config) if err != nil { // Don't wrap the error since it's informative enough as is. @@ -498,17 +537,8 @@ func parseConfig() (err error) { return fmt.Errorf("validating udp ports: %w", err) } - if !filtering.ValidateUpdateIvl(config.DNS.DnsfilterConf.FiltersUpdateIntervalHours) { - config.DNS.DnsfilterConf.FiltersUpdateIntervalHours = 24 - } - - if config.DNS.UpstreamTimeout.Duration == 0 { - config.DNS.UpstreamTimeout = timeutil.Duration{Duration: dnsforward.DefaultTimeout} - } - - err = setContextTLSCipherIDs() - if err != nil { - return err + if !filtering.ValidateUpdateIvl(config.Filtering.FiltersUpdateIntervalHours) { + config.Filtering.FiltersUpdateIntervalHours = 24 } return nil @@ -563,7 +593,6 @@ func (c *configuration) write() (err error) { config.Stats.Interval = timeutil.Duration{Duration: statsConf.Limit} config.Stats.Enabled = statsConf.Enabled config.Stats.Ignored = statsConf.Ignored.Values() - slices.Sort(config.Stats.Ignored) } if Context.queryLog != nil { @@ -575,21 +604,20 @@ func (c *configuration) write() (err error) { config.QueryLog.Interval = timeutil.Duration{Duration: dc.RotationIvl} config.QueryLog.MemSize = dc.MemSize config.QueryLog.Ignored = dc.Ignored.Values() - slices.Sort(config.Stats.Ignored) } if Context.filters != nil { - Context.filters.WriteDiskConfig(config.DNS.DnsfilterConf) - config.Filters = config.DNS.DnsfilterConf.Filters - config.WhitelistFilters = config.DNS.DnsfilterConf.WhitelistFilters - config.UserRules = config.DNS.DnsfilterConf.UserRules + Context.filters.WriteDiskConfig(config.Filtering) + config.Filters = config.Filtering.Filters + config.WhitelistFilters = config.Filtering.WhitelistFilters + config.UserRules = config.Filtering.UserRules } if s := Context.dnsServer; s != nil { - c := dnsforward.FilteringConfig{} + c := dnsforward.Config{} s.WriteDiskConfig(&c) dns := &config.DNS - dns.FilteringConfig = c + dns.Config = c dns.LocalPTRResolvers = s.LocalPTRResolvers() diff --git a/internal/home/control.go b/internal/home/control.go index a2414b35..4a675e93 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -127,12 +127,12 @@ func handleStatus(w http.ResponseWriter, r *http.Request) { } var ( - fltConf *dnsforward.FilteringConfig + fltConf *dnsforward.Config protectionDisabledUntil *time.Time protectionEnabled bool ) if Context.dnsServer != nil { - fltConf = &dnsforward.FilteringConfig{} + fltConf = &dnsforward.Config{} Context.dnsServer.WriteDiskConfig(fltConf) protectionEnabled, protectionDisabledUntil = Context.dnsServer.UpdatedProtectionStatus() } @@ -170,7 +170,7 @@ func handleStatus(w http.ResponseWriter, r *http.Request) { resp.IsDHCPAvailable = Context.dhcpServer != nil } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // ------------------------ @@ -321,9 +321,10 @@ func preInstallHandler(handler http.Handler) http.Handler { return &preInstallHandlerStruct{handler} } -// handleHTTPSRedirect redirects the request to HTTPS, if needed. If ok is -// true, the middleware must continue handling the request. -func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) { +// handleHTTPSRedirect redirects the request to HTTPS, if needed, and adds some +// HTTPS-related headers. If proceed is true, the middleware must continue +// handling the request. +func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (proceed bool) { web := Context.web if web.httpsServer.server == nil { return true @@ -362,21 +363,17 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) { respHdr.Set(httphdr.AltSvc, altSvc) } - if r.TLS == nil && forceHTTPS { - hostPort := host - if portHTTPS != defaultPortHTTPS { - hostPort = netutil.JoinHostPort(host, portHTTPS) + if forceHTTPS { + if r.TLS == nil { + u := httpsURL(r.URL, host, portHTTPS) + http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect) + + return false } - httpsURL := &url.URL{ - Scheme: aghhttp.SchemeHTTPS, - Host: hostPort, - Path: r.URL.Path, - RawQuery: r.URL.RawQuery, - } - http.Redirect(w, r, httpsURL.String(), http.StatusTemporaryRedirect) - - return false + // TODO(a.garipov): Consider adding a configurable max-age. Currently, + // the default is 365 days. + respHdr.Set(httphdr.StrictTransportSecurity, aghhttp.HdrValStrictTransportSecurity) } // Allow the frontend from the HTTP origin to send requests to the HTTPS @@ -395,6 +392,22 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) { return true } +// httpsURL returns a copy of u for redirection to the HTTPS version, taking the +// hostname and the HTTPS port into account. +func httpsURL(u *url.URL, host string, portHTTPS int) (redirectURL *url.URL) { + hostPort := host + if portHTTPS != defaultPortHTTPS { + hostPort = netutil.JoinHostPort(host, portHTTPS) + } + + return &url.URL{ + Scheme: aghhttp.SchemeHTTPS, + Host: hostPort, + Path: u.Path, + RawQuery: u.RawQuery, + } +} + // postInstall lets the handler to run only if firstRun is false. Otherwise, it // redirects to /install.html. It also enforces HTTPS if it is enabled and // configured and sets appropriate access control headers. @@ -408,11 +421,10 @@ func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.Res return } - if !handleHTTPSRedirect(w, r) { - return + proceed := handleHTTPSRedirect(w, r) + if proceed { + handler(w, r) } - - handler(w, r) } } diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index a5be3354..d6985eab 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -59,7 +59,7 @@ func (web *webAPI) handleInstallGetAddresses(w http.ResponseWriter, r *http.Requ data.Interfaces[iface.Name] = iface } - _ = aghhttp.WriteJSONResponse(w, r, data) + aghhttp.WriteJSONResponseOK(w, r, data) } type checkConfReqEnt struct { @@ -190,7 +190,7 @@ func (web *webAPI) handleInstallCheckConfig(w http.ResponseWriter, r *http.Reque resp.StaticIP = handleStaticIP(req.DNS.IP, req.SetStaticIP) } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // handleStaticIP - handles static IP request diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go index 5238134c..50a1a6f3 100644 --- a/internal/home/controlupdate.go +++ b/internal/home/controlupdate.go @@ -33,7 +33,7 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) { resp := &versionResponse{} if web.conf.disableUpdate { resp.Disabled = true - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) return } @@ -68,7 +68,7 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) { return } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // requestVersionInfo sets the VersionInfo field of resp if it can reach the diff --git a/internal/home/dns.go b/internal/home/dns.go index cb6a5142..59f3a71e 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -14,7 +14,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/client" - "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/querylog" @@ -60,12 +59,12 @@ func initDNS() (err error) { ShouldCountClient: Context.clients.shouldCountClient, } - set, err := aghnet.NewDomainNameSet(config.Stats.Ignored) + engine, err := aghnet.NewIgnoreEngine(config.Stats.Ignored) if err != nil { return fmt.Errorf("statistics: ignored list: %w", err) } - statsConf.Ignored = set + statsConf.Ignored = engine Context.stats, err = stats.New(statsConf) if err != nil { return fmt.Errorf("init stats: %w", err) @@ -84,18 +83,18 @@ func initDNS() (err error) { FileEnabled: config.QueryLog.FileEnabled, } - set, err = aghnet.NewDomainNameSet(config.QueryLog.Ignored) + engine, err = aghnet.NewIgnoreEngine(config.QueryLog.Ignored) if err != nil { return fmt.Errorf("querylog: ignored list: %w", err) } - conf.Ignored = set + conf.Ignored = engine Context.queryLog, err = querylog.New(conf) if err != nil { return fmt.Errorf("init querylog: %w", err) } - Context.filters, err = filtering.New(config.DNS.DnsfilterConf, nil) + Context.filters, err = filtering.New(config.Filtering, nil) if err != nil { // Don't wrap the error, since it's informative enough as is. return err @@ -123,7 +122,7 @@ func initDNSServer( filters *filtering.DNSFilter, sts stats.Interface, qlog querylog.QueryLog, - dhcpSrv dhcpd.Interface, + dhcpSrv dnsforward.DHCP, anonymizer *aghnet.IPMut, httpReg aghhttp.RegisterFunc, tlsConf *tlsConfigSettings, @@ -231,13 +230,13 @@ func newServerConfig( hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()}) newConf = &dnsforward.ServerConfig{ - UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port), - TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port), - FilteringConfig: dnsConf.FilteringConfig, - ConfigModified: onConfigModified, - HTTPRegister: httpReg, - UseDNS64: config.DNS.UseDNS64, - DNS64Prefixes: config.DNS.DNS64Prefixes, + UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port), + TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port), + Config: dnsConf.Config, + ConfigModified: onConfigModified, + HTTPRegister: httpReg, + UseDNS64: config.DNS.UseDNS64, + DNS64Prefixes: config.DNS.DNS64Prefixes, } var initialAddresses []netip.Addr @@ -378,7 +377,7 @@ func getDNSEncryption() (de dnsEncryption) { // applyAdditionalFiltering adds additional client information and settings if // the client has them. -func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering.Settings) { +func applyAdditionalFiltering(clientIP netip.Addr, clientID string, setts *filtering.Settings) { // pref is a prefix for logging messages around the scope. const pref = "applying filters" @@ -386,7 +385,7 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering log.Debug("%s: looking for client with ip %s and clientid %q", pref, clientIP, clientID) - if clientIP == nil { + if !clientIP.IsValid() { return } @@ -502,14 +501,10 @@ func closeDNSServer() { if err != nil { log.Debug("closing stats: %s", err) } - - // TODO(e.burkov): Find out if it's safe. - Context.stats = nil } if Context.queryLog != nil { Context.queryLog.Close() - Context.queryLog = nil } log.Debug("all dns modules are closed") diff --git a/internal/home/dns_internal_test.go b/internal/home/dns_internal_test.go index 8ba988f2..22fa06fe 100644 --- a/internal/home/dns_internal_test.go +++ b/internal/home/dns_internal_test.go @@ -1,7 +1,7 @@ package home import ( - "net" + "net/netip" "testing" "github.com/AdguardTeam/AdGuardHome/internal/filtering" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/require" ) +var testIPv4 = netip.AddrFrom4([4]byte{1, 2, 3, 4}) + func TestApplyAdditionalFiltering(t *testing.T) { var err error @@ -78,7 +80,7 @@ func TestApplyAdditionalFiltering(t *testing.T) { t.Run(tc.name, func(t *testing.T) { setts := &filtering.Settings{} - applyAdditionalFiltering(net.IP{1, 2, 3, 4}, tc.id, setts) + applyAdditionalFiltering(testIPv4, tc.id, setts) tc.FilteringEnabled(t, setts.FilteringEnabled) tc.SafeSearchEnabled(t, setts.SafeSearchEnabled) tc.SafeBrowsingEnabled(t, setts.SafeBrowsingEnabled) @@ -169,7 +171,7 @@ func TestApplyAdditionalFiltering_blockedServices(t *testing.T) { t.Run(tc.name, func(t *testing.T) { setts := &filtering.Settings{} - applyAdditionalFiltering(net.IP{1, 2, 3, 4}, tc.id, setts) + applyAdditionalFiltering(testIPv4, tc.id, setts) require.Len(t, setts.ServicesRules, tc.wantLen) }) } diff --git a/internal/home/home.go b/internal/home/home.go index 60eb63dc..eae9422a 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -22,6 +22,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/AdGuardHome/internal/aghtls" + "github.com/AdguardTeam/AdGuardHome/internal/arpdb" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/filtering" @@ -37,12 +38,6 @@ import ( "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" "golang.org/x/exp/slices" - "gopkg.in/natefinch/lumberjack.v2" -) - -const ( - // Used in config to indicate that syslog or eventlog (win) should be used for logger output - configSyslog = "syslog" ) // Global context @@ -104,8 +99,11 @@ func Main(clientBuildFS fs.FS) { // package flag. opts := loadCmdLineOpts() + done := make(chan struct{}) + signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) + go func() { for { sig := <-signals @@ -117,19 +115,19 @@ func Main(clientBuildFS fs.FS) { default: cleanup(context.Background()) cleanupAlways() - os.Exit(0) + close(done) } } }() if opts.serviceControlAction != "" { - handleServiceControlAction(opts, clientBuildFS, signals) + handleServiceControlAction(opts, clientBuildFS, signals, done) return } // run the protection - run(opts, clientBuildFS) + run(opts, clientBuildFS, done) } // setupContext initializes [Context] fields. It also reads and upgrades @@ -147,14 +145,8 @@ func setupContext(opts options) (err error) { return nil } - // Do the upgrade if necessary. - err = upgradeConfig() + err = parseConfig() if err != nil { - // Don't wrap the error, because it's informative enough as is. - return err - } - - if err = parseConfig(); err != nil { log.Error("parsing configuration file: %s", err) os.Exit(1) @@ -239,7 +231,6 @@ func setupHostsContainer() (err error) { } Context.etcHosts, err = aghnet.NewHostsContainer( - filtering.SysHostsListID, aghos.RootDirFS(), hostsWatcher, aghnet.DefaultHostsPaths()..., @@ -275,7 +266,7 @@ func setupOpts(opts options) (err error) { // initContextClients initializes Context clients and related fields. func initContextClients() (err error) { - err = setupDNSFilteringConf(config.DNS.DnsfilterConf) + err = setupDNSFilteringConf(config.Filtering) if err != nil { // Don't wrap the error, because it's informative enough as is. return err @@ -296,17 +287,17 @@ func initContextClients() (err error) { return fmt.Errorf("initing dhcp: %w", err) } - var arpdb aghnet.ARPDB + var arpDB arpdb.Interface if config.Clients.Sources.ARP { - arpdb = aghnet.NewARPDB() + arpDB = arpdb.New() } err = Context.clients.Init( config.Clients.Persistent, Context.dhcpServer, Context.etcHosts, - arpdb, - config.DNS.DnsfilterConf, + arpDB, + config.Filtering, ) if err != nil { // Don't wrap the error, because it's informative enough as is. @@ -368,6 +359,9 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) { pcService = "parental control" defaultParentalServer = `https://family.adguard-dns.com/dns-query` pcTXTSuffix = `pc.dns.adguard.com.` + + defaultSafeBrowsingBlockHost = "standard-block.dns.adguard.com" + defaultParentalBlockHost = "family-block.dns.adguard.com" ) conf.EtcHosts = Context.etcHosts @@ -404,6 +398,10 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) { CacheSize: conf.SafeBrowsingCacheSize, }) + if conf.SafeBrowsingBlockHost != "" { + conf.SafeBrowsingBlockHost = defaultSafeBrowsingBlockHost + } + parUps, err := upstream.AddressToUpstream(defaultParentalServer, upsOpts) if err != nil { return fmt.Errorf("converting parental server: %w", err) @@ -417,6 +415,10 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) { CacheSize: conf.ParentalCacheSize, }) + if conf.ParentalBlockHost != "" { + conf.ParentalBlockHost = defaultParentalBlockHost + } + conf.SafeSearchConf.CustomResolver = safeSearchResolver{} conf.SafeSearch, err = safesearch.NewDefault( conf.SafeSearchConf, @@ -510,7 +512,7 @@ func fatalOnError(err error) { } // run configures and starts AdGuard Home. -func run(opts options, clientBuildFS fs.FS) { +func run(opts options, clientBuildFS fs.FS, done chan struct{}) { // Configure config filename. initConfigFilename(opts) @@ -547,7 +549,7 @@ func run(opts options, clientBuildFS fs.FS) { fatalOnError(err) upd := updater.NewUpdater(&updater.Config{ - Client: config.DNS.DnsfilterConf.HTTPClient, + Client: config.Filtering.HTTPClient, Version: version.Version(), Channel: version.Channel(), GOARCH: runtime.GOARCH, @@ -567,9 +569,8 @@ func run(opts options, clientBuildFS fs.FS) { err = config.write() fatalOnError(err) - if config.DebugPProf { - // TODO(a.garipov): Make the address configurable. - startPprof("localhost:6060") + if config.HTTPConfig.Pprof.Enabled { + startPprof(config.HTTPConfig.Pprof.Port) } } @@ -616,8 +617,8 @@ func run(opts options, clientBuildFS fs.FS) { Context.web.start() - // Wait indefinitely for other goroutines to complete their job. - select {} + // Wait for other goroutines to complete their job. + <-done } // initUsers initializes context auth module. Clears config users field. @@ -748,79 +749,6 @@ func initWorkingDir(opts options) (err error) { return nil } -// configureLogger configures logger level and output. -func configureLogger(opts options) (err error) { - ls := getLogSettings(opts) - - // Configure logger level. - if ls.Verbose { - log.SetLevel(log.DEBUG) - } - - // Make sure that we see the microseconds in logs, as networking stuff can - // happen pretty quickly. - log.SetFlags(log.LstdFlags | log.Lmicroseconds) - - // Write logs to stdout by default. - if ls.File == "" { - return nil - } - - if ls.File == configSyslog { - // Use syslog where it is possible and eventlog on Windows. - err = aghos.ConfigureSyslog(serviceName) - if err != nil { - return fmt.Errorf("cannot initialize syslog: %w", err) - } - - return nil - } - - logFilePath := ls.File - if !filepath.IsAbs(logFilePath) { - logFilePath = filepath.Join(Context.workDir, logFilePath) - } - - log.SetOutput(&lumberjack.Logger{ - Filename: logFilePath, - Compress: ls.Compress, - LocalTime: ls.LocalTime, - MaxBackups: ls.MaxBackups, - MaxSize: ls.MaxSize, - MaxAge: ls.MaxAge, - }) - - return nil -} - -// getLogSettings returns a log settings object properly initialized from opts. -func getLogSettings(opts options) (ls *logSettings) { - ls = readLogSettings() - configLogSettings := config.Log - - // Command-line arguments can override config settings. - if opts.verbose || configLogSettings.Verbose { - ls.Verbose = true - } - - ls.File = stringutil.Coalesce(opts.logFile, configLogSettings.File, ls.File) - - // Handle default log settings overrides. - ls.Compress = configLogSettings.Compress - ls.LocalTime = configLogSettings.LocalTime - ls.MaxBackups = configLogSettings.MaxBackups - ls.MaxSize = configLogSettings.MaxSize - ls.MaxAge = configLogSettings.MaxAge - - if opts.runningAsService && ls.File == "" && runtime.GOOS == "windows" { - // When running as a Windows service, use eventlog by default if - // nothing else is configured. Otherwise, we'll lose the log output. - ls.File = configSyslog - } - - return ls -} - // cleanup stops and resets all the modules. func cleanup(ctx context.Context) { log.Info("stopping AdGuard Home") diff --git a/internal/home/home_test.go b/internal/home/home_test.go index 2ce1d76d..c56f3955 100644 --- a/internal/home/home_test.go +++ b/internal/home/home_test.go @@ -7,6 +7,6 @@ import ( ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) initCmdLineOpts() + testutil.DiscardLogOutput(m) } diff --git a/internal/home/i18n.go b/internal/home/i18n.go index d9e3435c..267205d3 100644 --- a/internal/home/i18n.go +++ b/internal/home/i18n.go @@ -58,7 +58,7 @@ type languageJSON struct { func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) { log.Printf("home: language is %s", config.Language) - _ = aghhttp.WriteJSONResponse(w, r, &languageJSON{ + aghhttp.WriteJSONResponseOK(w, r, &languageJSON{ Language: config.Language, }) } diff --git a/internal/home/log.go b/internal/home/log.go new file mode 100644 index 00000000..28114800 --- /dev/null +++ b/internal/home/log.go @@ -0,0 +1,106 @@ +package home + +import ( + "fmt" + "path/filepath" + "runtime" + + "github.com/AdguardTeam/AdGuardHome/internal/aghos" + "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/stringutil" + "gopkg.in/natefinch/lumberjack.v2" + "gopkg.in/yaml.v3" +) + +// configSyslog is used to indicate that syslog or eventlog (win) should be used +// for logger output. +const configSyslog = "syslog" + +// configureLogger configures logger level and output. +func configureLogger(opts options) (err error) { + ls := getLogSettings(opts) + + // Configure logger level. + if ls.Verbose { + log.SetLevel(log.DEBUG) + } + + // Make sure that we see the microseconds in logs, as networking stuff can + // happen pretty quickly. + log.SetFlags(log.LstdFlags | log.Lmicroseconds) + + // Write logs to stdout by default. + if ls.File == "" { + return nil + } + + if ls.File == configSyslog { + // Use syslog where it is possible and eventlog on Windows. + err = aghos.ConfigureSyslog(serviceName) + if err != nil { + return fmt.Errorf("cannot initialize syslog: %w", err) + } + + return nil + } + + logFilePath := ls.File + if !filepath.IsAbs(logFilePath) { + logFilePath = filepath.Join(Context.workDir, logFilePath) + } + + log.SetOutput(&lumberjack.Logger{ + Filename: logFilePath, + Compress: ls.Compress, + LocalTime: ls.LocalTime, + MaxBackups: ls.MaxBackups, + MaxSize: ls.MaxSize, + MaxAge: ls.MaxAge, + }) + + return nil +} + +// getLogSettings returns a log settings object properly initialized from opts. +func getLogSettings(opts options) (ls *logSettings) { + configLogSettings := config.Log + + ls = readLogSettings() + if ls == nil { + // Use default log settings. + ls = &configLogSettings + } + + // Command-line arguments can override config settings. + if opts.verbose { + ls.Verbose = true + } + ls.File = stringutil.Coalesce(opts.logFile, ls.File) + + if opts.runningAsService && ls.File == "" && runtime.GOOS == "windows" { + // When running as a Windows service, use eventlog by default if + // nothing else is configured. Otherwise, we'll lose the log output. + ls.File = configSyslog + } + + return ls +} + +// readLogSettings reads logging settings from the config file. We do it in a +// separate method in order to configure logger before the actual configuration +// is parsed and applied. +func readLogSettings() (ls *logSettings) { + conf := &configuration{} + + yamlFile, err := readConfigFile() + if err != nil { + return nil + } + + err = yaml.Unmarshal(yamlFile, conf) + if err != nil { + log.Error("Couldn't get logging settings from the configuration: %s", err) + } + + return &conf.Log +} diff --git a/internal/home/profilehttp.go b/internal/home/profilehttp.go index 12e036c3..91e36f28 100644 --- a/internal/home/profilehttp.go +++ b/internal/home/profilehttp.go @@ -61,7 +61,7 @@ func handleGetProfile(w http.ResponseWriter, r *http.Request) { } }() - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // handlePutProfile is the handler for PUT /control/profile/update endpoint. diff --git a/internal/home/service.go b/internal/home/service.go index e98aa030..1a80ca07 100644 --- a/internal/home/service.go +++ b/internal/home/service.go @@ -34,6 +34,7 @@ const ( type program struct { clientBuildFS fs.FS signals chan os.Signal + done chan struct{} opts options } @@ -46,19 +47,19 @@ func (p *program) Start(_ service.Service) (err error) { args := p.opts args.runningAsService = true - go run(args, p.clientBuildFS) + go run(args, p.clientBuildFS, p.done) return nil } // Stop implements service.Interface interface for *program. func (p *program) Stop(_ service.Service) (err error) { - select { - case p.signals <- syscall.SIGINT: - // Go on. - default: - // Stop should not block. - } + log.Info("service: stopping: waiting for cleanup") + + aghos.SendShutdownSignal(p.signals) + + // Wait for other goroutines to complete their job. + <-p.done return nil } @@ -198,7 +199,12 @@ func restartService() (err error) { // - run: This is a special command that is not supposed to be used directly // it is specified when we register a service, and it indicates to the app // that it is being run as a service/daemon. -func handleServiceControlAction(opts options, clientBuildFS fs.FS, signals chan os.Signal) { +func handleServiceControlAction( + opts options, + clientBuildFS fs.FS, + signals chan os.Signal, + done chan struct{}, +) { // Call chooseSystem explicitly to introduce OpenBSD support for service // package. It's a noop for other GOOS values. chooseSystem() @@ -233,6 +239,7 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS, signals chan s, err := service.New(&program{ clientBuildFS: clientBuildFS, signals: signals, + done: done, opts: runOpts, }, svcConfig) if err != nil { diff --git a/internal/home/tls.go b/internal/home/tls.go index c42d5175..004e9412 100644 --- a/internal/home/tls.go +++ b/internal/home/tls.go @@ -770,7 +770,7 @@ func marshalTLS(w http.ResponseWriter, r *http.Request, data tlsConfig) { data.PrivateKey = "" } - _ = aghhttp.WriteJSONResponse(w, r, data) + aghhttp.WriteJSONResponseOK(w, r, data) } // registerWebHandlers registers HTTP handlers for TLS configuration. diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go deleted file mode 100644 index be9e2873..00000000 --- a/internal/home/upgrade.go +++ /dev/null @@ -1,1440 +0,0 @@ -package home - -import ( - "bytes" - "fmt" - "net/netip" - "net/url" - "os" - "path" - "path/filepath" - "runtime" - "strconv" - "strings" - "time" - - "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" - "github.com/AdguardTeam/golibs/netutil" - "github.com/AdguardTeam/golibs/timeutil" - "github.com/google/renameio/v2/maybe" - "golang.org/x/crypto/bcrypt" - yaml "gopkg.in/yaml.v3" -) - -// currentSchemaVersion is the current schema version. -const currentSchemaVersion = 24 - -// These aliases are provided for convenience. -type ( - yarr = []any - yobj = map[string]any -) - -// Performs necessary upgrade operations if needed -func upgradeConfig() error { - // read a config file into an interface map, so we can manipulate values without losing any - diskConf := yobj{} - body, err := readConfigFile() - if err != nil { - return err - } - - err = yaml.Unmarshal(body, &diskConf) - if err != nil { - log.Printf("parsing config file for upgrade: %s", err) - - return err - } - - schemaVersionInterface, ok := diskConf["schema_version"] - log.Tracef("got schema version %v", schemaVersionInterface) - if !ok { - // no schema version, set it to 0 - schemaVersionInterface = 0 - } - - schemaVersion, ok := schemaVersionInterface.(int) - if !ok { - err = fmt.Errorf("configuration file contains non-integer schema_version, abort") - log.Println(err) - return err - } - - if schemaVersion == currentSchemaVersion { - // do nothing - return nil - } - - return upgradeConfigSchema(schemaVersion, diskConf) -} - -// upgradeFunc is a function that upgrades a config and returns an error. -type upgradeFunc = func(diskConf yobj) (err error) - -// Upgrade from oldVersion to newVersion -func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) { - upgrades := []upgradeFunc{ - upgradeSchema0to1, - upgradeSchema1to2, - upgradeSchema2to3, - upgradeSchema3to4, - upgradeSchema4to5, - upgradeSchema5to6, - upgradeSchema6to7, - upgradeSchema7to8, - upgradeSchema8to9, - upgradeSchema9to10, - upgradeSchema10to11, - upgradeSchema11to12, - upgradeSchema12to13, - upgradeSchema13to14, - upgradeSchema14to15, - upgradeSchema15to16, - upgradeSchema16to17, - upgradeSchema17to18, - upgradeSchema18to19, - upgradeSchema19to20, - upgradeSchema20to21, - upgradeSchema21to22, - upgradeSchema22to23, - upgradeSchema23to24, - } - - n := 0 - for i, u := range upgrades { - if i >= oldVersion { - err = u(diskConf) - if err != nil { - return err - } - - n++ - } - } - - if n == 0 { - return fmt.Errorf("unknown configuration schema version %d", oldVersion) - } - - buf := &bytes.Buffer{} - enc := yaml.NewEncoder(buf) - enc.SetIndent(2) - - err = enc.Encode(diskConf) - if err != nil { - return fmt.Errorf("generating new config: %w", err) - } - - config.fileData = buf.Bytes() - confFile := config.getConfigFilename() - err = maybe.WriteFile(confFile, config.fileData, 0o644) - if err != nil { - return fmt.Errorf("writing new config: %w", err) - } - - return nil -} - -// The first schema upgrade: -// No more "dnsfilter.txt", filters are now kept in data/filters/ -func upgradeSchema0to1(diskConf yobj) (err error) { - log.Printf("%s(): called", funcName()) - - dnsFilterPath := filepath.Join(Context.workDir, "dnsfilter.txt") - log.Printf("deleting %s as we don't need it anymore", dnsFilterPath) - err = os.Remove(dnsFilterPath) - if err != nil && !errors.Is(err, os.ErrNotExist) { - log.Info("warning: %s", err) - - // Go on. - } - - diskConf["schema_version"] = 1 - - return nil -} - -// Second schema upgrade: -// coredns is now dns in config -// delete 'Corefile', since we don't use that anymore -func upgradeSchema1to2(diskConf yobj) (err error) { - log.Printf("%s(): called", funcName()) - - coreFilePath := filepath.Join(Context.workDir, "Corefile") - log.Printf("deleting %s as we don't need it anymore", coreFilePath) - err = os.Remove(coreFilePath) - if err != nil && !errors.Is(err, os.ErrNotExist) { - log.Info("warning: %s", err) - - // Go on. - } - - if _, ok := diskConf["dns"]; !ok { - diskConf["dns"] = diskConf["coredns"] - delete(diskConf, "coredns") - } - diskConf["schema_version"] = 2 - - return nil -} - -// Third schema upgrade: -// Bootstrap DNS becomes an array -func upgradeSchema2to3(diskConf yobj) error { - log.Printf("%s(): called", funcName()) - - // Let's read dns configuration from diskConf - dnsConfig, ok := diskConf["dns"] - if !ok { - return fmt.Errorf("no DNS configuration in config file") - } - - // Convert any to yobj - newDNSConfig := make(yobj) - - switch v := dnsConfig.(type) { - case yobj: - for k, v := range v { - newDNSConfig[fmt.Sprint(k)] = v - } - default: - return fmt.Errorf("unexpected type of dns: %T", dnsConfig) - } - - // Replace bootstrap_dns value filed with new array contains old bootstrap_dns inside - bootstrapDNS, ok := newDNSConfig["bootstrap_dns"] - if !ok { - return fmt.Errorf("no bootstrap DNS in DNS config") - } - - newBootstrapConfig := []string{fmt.Sprint(bootstrapDNS)} - newDNSConfig["bootstrap_dns"] = newBootstrapConfig - diskConf["dns"] = newDNSConfig - - // Bump schema version - diskConf["schema_version"] = 3 - - return nil -} - -// Add use_global_blocked_services=true setting for existing "clients" array -func upgradeSchema3to4(diskConf yobj) error { - log.Printf("%s(): called", funcName()) - - diskConf["schema_version"] = 4 - - clients, ok := diskConf["clients"] - if !ok { - return nil - } - - switch arr := clients.(type) { - case []any: - - for i := range arr { - switch c := arr[i].(type) { - - case map[any]any: - c["use_global_blocked_services"] = true - - default: - continue - } - } - - default: - return nil - } - - return nil -} - -// Replace "auth_name", "auth_pass" string settings with an array: -// users: -// - name: "..." -// password: "..." -// -// ... -func upgradeSchema4to5(diskConf yobj) error { - log.Printf("%s(): called", funcName()) - - diskConf["schema_version"] = 5 - - name, ok := diskConf["auth_name"] - if !ok { - return nil - } - nameStr, ok := name.(string) - if !ok { - log.Fatal("Please use double quotes in your user name in \"auth_name\" and restart AdGuardHome") - return nil - } - - pass, ok := diskConf["auth_pass"] - if !ok { - return nil - } - passStr, ok := pass.(string) - if !ok { - log.Fatal("Please use double quotes in your password in \"auth_pass\" and restart AdGuardHome") - return nil - } - - if len(nameStr) == 0 { - return nil - } - - hash, err := bcrypt.GenerateFromPassword([]byte(passStr), bcrypt.DefaultCost) - if err != nil { - log.Fatalf("Can't use password \"%s\": bcrypt.GenerateFromPassword: %s", passStr, err) - return nil - } - u := webUser{ - Name: nameStr, - PasswordHash: string(hash), - } - users := []webUser{u} - diskConf["users"] = users - return nil -} - -// upgradeSchema5to6 performs the following changes: -// -// # BEFORE: -// 'clients': -// ... -// 'ip': 127.0.0.1 -// 'mac': ... -// -// # AFTER: -// 'clients': -// ... -// 'ids': -// - 127.0.0.1 -// - ... -func upgradeSchema5to6(diskConf yobj) error { - log.Printf("Upgrade yaml: 5 to 6") - diskConf["schema_version"] = 6 - - clientsVal, ok := diskConf["clients"] - if !ok { - return nil - } - - clients, ok := clientsVal.([]yobj) - if !ok { - return fmt.Errorf("unexpected type of clients: %T", clientsVal) - } - - for i := range clients { - c := clients[i] - var ids []string - - if ipVal, hasIP := c["ip"]; hasIP { - var ip string - if ip, ok = ipVal.(string); !ok { - return fmt.Errorf("client.ip is not a string: %v", ipVal) - } - - if ip != "" { - ids = append(ids, ip) - } - } - - if macVal, hasMac := c["mac"]; hasMac { - var mac string - if mac, ok = macVal.(string); !ok { - return fmt.Errorf("client.mac is not a string: %v", macVal) - } - - if mac != "" { - ids = append(ids, mac) - } - } - - c["ids"] = ids - } - - return nil -} - -// dhcp: -// -// enabled: false -// interface_name: vboxnet0 -// gateway_ip: 192.168.56.1 -// ... -// -// -> -// -// dhcp: -// -// enabled: false -// interface_name: vboxnet0 -// dhcpv4: -// gateway_ip: 192.168.56.1 -// ... -func upgradeSchema6to7(diskConf yobj) error { - log.Printf("Upgrade yaml: 6 to 7") - - diskConf["schema_version"] = 7 - - dhcpVal, ok := diskConf["dhcp"] - if !ok { - return nil - } - - switch dhcp := dhcpVal.(type) { - case map[any]any: - var str string - str, ok = dhcp["gateway_ip"].(string) - if !ok { - log.Fatalf("expecting dhcp.%s to be a string", "gateway_ip") - return nil - } - - dhcpv4 := yobj{ - "gateway_ip": str, - } - delete(dhcp, "gateway_ip") - - str, ok = dhcp["subnet_mask"].(string) - if !ok { - log.Fatalf("expecting dhcp.%s to be a string", "subnet_mask") - return nil - } - dhcpv4["subnet_mask"] = str - delete(dhcp, "subnet_mask") - - str, ok = dhcp["range_start"].(string) - if !ok { - log.Fatalf("expecting dhcp.%s to be a string", "range_start") - return nil - } - dhcpv4["range_start"] = str - delete(dhcp, "range_start") - - str, ok = dhcp["range_end"].(string) - if !ok { - log.Fatalf("expecting dhcp.%s to be a string", "range_end") - return nil - } - dhcpv4["range_end"] = str - delete(dhcp, "range_end") - - var n int - n, ok = dhcp["lease_duration"].(int) - if !ok { - log.Fatalf("expecting dhcp.%s to be an integer", "lease_duration") - return nil - } - dhcpv4["lease_duration"] = n - delete(dhcp, "lease_duration") - - n, ok = dhcp["icmp_timeout_msec"].(int) - if !ok { - log.Fatalf("expecting dhcp.%s to be an integer", "icmp_timeout_msec") - return nil - } - dhcpv4["icmp_timeout_msec"] = n - delete(dhcp, "icmp_timeout_msec") - - dhcp["dhcpv4"] = dhcpv4 - default: - return nil - } - - return nil -} - -// upgradeSchema7to8 performs the following changes: -// -// # BEFORE: -// 'dns': -// 'bind_host': '127.0.0.1' -// -// # AFTER: -// 'dns': -// 'bind_hosts': -// - '127.0.0.1' -func upgradeSchema7to8(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 7 to 8") - - diskConf["schema_version"] = 8 - - dnsVal, ok := diskConf["dns"] - if !ok { - return nil - } - - dns, ok := dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - bindHostVal := dns["bind_host"] - bindHost, ok := bindHostVal.(string) - if !ok { - return fmt.Errorf("unexpected type of dns.bind_host: %T", bindHostVal) - } - - delete(dns, "bind_host") - dns["bind_hosts"] = yarr{bindHost} - - return nil -} - -// upgradeSchema8to9 performs the following changes: -// -// # BEFORE: -// 'dns': -// 'autohost_tld': 'lan' -// -// # AFTER: -// 'dns': -// 'local_domain_name': 'lan' -func upgradeSchema8to9(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 8 to 9") - - diskConf["schema_version"] = 9 - - dnsVal, ok := diskConf["dns"] - if !ok { - return nil - } - - dns, ok := dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - autohostTLDVal, ok := dns["autohost_tld"] - if !ok { - // This happens when upgrading directly from v0.105.2, because - // dns.autohost_tld was never set to any value. Go on and leave - // it that way. - // - // See https://github.com/AdguardTeam/AdGuardHome/issues/2988. - return nil - } - - autohostTLD, ok := autohostTLDVal.(string) - if !ok { - return fmt.Errorf("unexpected type of dns.autohost_tld: %T", autohostTLDVal) - } - - delete(dns, "autohost_tld") - dns["local_domain_name"] = autohostTLD - - return nil -} - -// addQUICPort inserts a port into QUIC upstream's hostname if it is missing. -func addQUICPort(ups string, port int) (withPort string) { - if ups == "" || ups[0] == '#' { - return ups - } - - var doms string - withPort = ups - if strings.HasPrefix(ups, "[/") { - domsAndUps := strings.Split(strings.TrimPrefix(ups, "[/"), "/]") - if len(domsAndUps) != 2 { - return ups - } - - doms, withPort = "[/"+domsAndUps[0]+"/]", domsAndUps[1] - } - - if !strings.Contains(withPort, "://") { - return ups - } - - upsURL, err := url.Parse(withPort) - if err != nil || upsURL.Scheme != "quic" { - return ups - } - - var host string - host, err = netutil.SplitHost(upsURL.Host) - if err != nil || host != upsURL.Host { - return ups - } - - upsURL.Host = strings.Join([]string{host, strconv.Itoa(port)}, ":") - - return doms + upsURL.String() -} - -// upgradeSchema9to10 performs the following changes: -// -// # BEFORE: -// 'dns': -// 'upstream_dns': -// - 'quic://some-upstream.com' -// -// # AFTER: -// 'dns': -// 'upstream_dns': -// - 'quic://some-upstream.com:784' -func upgradeSchema9to10(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 9 to 10") - - diskConf["schema_version"] = 10 - - dnsVal, ok := diskConf["dns"] - if !ok { - return nil - } - - var dns yobj - dns, ok = dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - const quicPort = 784 - for _, upsField := range []string{ - "upstream_dns", - "local_ptr_upstreams", - } { - var upsVal any - upsVal, ok = dns[upsField] - if !ok { - continue - } - var ups yarr - ups, ok = upsVal.(yarr) - if !ok { - return fmt.Errorf("unexpected type of dns.%s: %T", upsField, upsVal) - } - - var u string - for i, uVal := range ups { - u, ok = uVal.(string) - if !ok { - return fmt.Errorf("unexpected type of upstream field: %T", uVal) - } - - ups[i] = addQUICPort(u, quicPort) - } - dns[upsField] = ups - } - - return nil -} - -// upgradeSchema10to11 performs the following changes: -// -// # BEFORE: -// 'rlimit_nofile': 42 -// -// # AFTER: -// 'os': -// 'group': '' -// 'rlimit_nofile': 42 -// 'user': '' -func upgradeSchema10to11(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 10 to 11") - - diskConf["schema_version"] = 11 - - rlimit := 0 - rlimitVal, ok := diskConf["rlimit_nofile"] - if ok { - rlimit, ok = rlimitVal.(int) - if !ok { - return fmt.Errorf("unexpected type of rlimit_nofile: %T", rlimitVal) - } - } - - delete(diskConf, "rlimit_nofile") - diskConf["os"] = yobj{ - "group": "", - "rlimit_nofile": rlimit, - "user": "", - } - - return nil -} - -// upgradeSchema11to12 performs the following changes: -// -// # BEFORE: -// 'querylog_interval': 90 -// -// # AFTER: -// 'querylog_interval': '2160h' -func upgradeSchema11to12(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 11 to 12") - diskConf["schema_version"] = 12 - - dnsVal, ok := diskConf["dns"] - if !ok { - return nil - } - - var dns yobj - dns, ok = dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - const field = "querylog_interval" - - // Set the initial value from home.initConfig function. - qlogIvl := 90 - qlogIvlVal, ok := dns[field] - if ok { - qlogIvl, ok = qlogIvlVal.(int) - if !ok { - return fmt.Errorf("unexpected type of %s: %T", field, qlogIvlVal) - } - } - - dns[field] = timeutil.Duration{Duration: time.Duration(qlogIvl) * timeutil.Day} - - return nil -} - -// upgradeSchema12to13 performs the following changes: -// -// # BEFORE: -// 'dns': -// # … -// 'local_domain_name': 'lan' -// -// # AFTER: -// 'dhcp': -// # … -// 'local_domain_name': 'lan' -func upgradeSchema12to13(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 12 to 13") - diskConf["schema_version"] = 13 - - dnsVal, ok := diskConf["dns"] - if !ok { - return nil - } - - var dns yobj - dns, ok = dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - dhcpVal, ok := diskConf["dhcp"] - if !ok { - return nil - } - - var dhcp yobj - dhcp, ok = dhcpVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dhcp: %T", dhcpVal) - } - - const field = "local_domain_name" - - dhcp[field] = dns[field] - delete(dns, field) - - return nil -} - -// upgradeSchema13to14 performs the following changes: -// -// # BEFORE: -// 'clients': -// - 'name': 'client-name' -// # … -// -// # AFTER: -// 'clients': -// 'persistent': -// - 'name': 'client-name' -// # … -// 'runtime_sources': -// 'whois': true -// 'arp': true -// 'rdns': true -// 'dhcp': true -// 'hosts': true -func upgradeSchema13to14(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 13 to 14") - diskConf["schema_version"] = 14 - - clientsVal, ok := diskConf["clients"] - if !ok { - clientsVal = yarr{} - } - - var rdnsSrc bool - if dnsVal, dok := diskConf["dns"]; dok { - var dnsSettings yobj - dnsSettings, ok = dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - var rdnsSrcVal any - rdnsSrcVal, ok = dnsSettings["resolve_clients"] - if ok { - rdnsSrc, ok = rdnsSrcVal.(bool) - if !ok { - return fmt.Errorf("unexpected type of resolve_clients: %T", rdnsSrcVal) - } - - delete(dnsSettings, "resolve_clients") - } - } - - diskConf["clients"] = yobj{ - "persistent": clientsVal, - "runtime_sources": &clientSourcesConfig{ - WHOIS: true, - ARP: true, - RDNS: rdnsSrc, - DHCP: true, - HostsFile: true, - }, - } - - return nil -} - -// upgradeSchema14to15 performs the following changes: -// -// # BEFORE: -// 'dns': -// 'querylog_enabled': true -// 'querylog_file_enabled': true -// 'querylog_interval': '2160h' -// 'querylog_size_memory': 1000 -// -// # AFTER: -// 'querylog': -// 'enabled': true -// 'file_enabled': true -// 'interval': '2160h' -// 'size_memory': 1000 -// 'ignored': [] -func upgradeSchema14to15(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 14 to 15") - diskConf["schema_version"] = 15 - - dnsVal, ok := diskConf["dns"] - if !ok { - return nil - } - - dns, ok := dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - type temp struct { - val any - from string - to string - } - replaces := []temp{ - {from: "querylog_enabled", to: "enabled", val: true}, - {from: "querylog_file_enabled", to: "file_enabled", val: true}, - {from: "querylog_interval", to: "interval", val: "2160h"}, - {from: "querylog_size_memory", to: "size_memory", val: 1000}, - } - qlog := map[string]any{ - "ignored": []any{}, - } - for _, r := range replaces { - v, has := dns[r.from] - if !has { - v = r.val - } - delete(dns, r.from) - qlog[r.to] = v - } - diskConf["querylog"] = qlog - - return nil -} - -// upgradeSchema15to16 performs the following changes: -// -// # BEFORE: -// 'dns': -// 'statistics_interval': 1 -// -// # AFTER: -// 'statistics': -// 'enabled': true -// 'interval': 1 -// 'ignored': [] -// -// If statistics were disabled: -// -// # BEFORE: -// 'dns': -// 'statistics_interval': 0 -// -// # AFTER: -// 'statistics': -// 'enabled': false -// 'interval': 1 -// 'ignored': [] -func upgradeSchema15to16(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 15 to 16") - diskConf["schema_version"] = 16 - - dnsVal, ok := diskConf["dns"] - if !ok { - return nil - } - - dns, ok := dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - stats := map[string]any{ - "enabled": true, - "interval": 1, - "ignored": []any{}, - } - - const field = "statistics_interval" - statsIvlVal, has := dns[field] - if has { - var statsIvl int - statsIvl, ok = statsIvlVal.(int) - if !ok { - return fmt.Errorf("unexpected type of dns.statistics_interval: %T", statsIvlVal) - } - - if statsIvl == 0 { - // Set the interval to the default value of one day to make sure - // that it passes the validations. - stats["interval"] = 1 - stats["enabled"] = false - } else { - stats["interval"] = statsIvl - stats["enabled"] = true - } - } - delete(dns, field) - - diskConf["statistics"] = stats - - return nil -} - -// 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: -// 'dns': -// 'safesearch_enabled': true -// -// # AFTER: -// 'dns': -// 'safe_search': -// 'enabled': true -// 'bing': true -// 'duckduckgo': true -// 'google': true -// 'pixabay': true -// 'yandex': true -// 'youtube': true -func upgradeSchema17to18(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 17 to 18") - diskConf["schema_version"] = 18 - - dnsVal, ok := diskConf["dns"] - if !ok { - return nil - } - - dns, ok := dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - safeSearch := yobj{ - "enabled": true, - "bing": true, - "duckduckgo": true, - "google": true, - "pixabay": true, - "yandex": true, - "youtube": true, - } - - const safeSearchKey = "safesearch_enabled" - - v, has := dns[safeSearchKey] - if has { - safeSearch["enabled"] = v - } - delete(dns, safeSearchKey) - - dns["safe_search"] = safeSearch - - return nil -} - -// upgradeSchema18to19 performs the following changes: -// -// # BEFORE: -// 'clients': -// 'persistent': -// - 'name': 'client-name' -// 'safesearch_enabled': true -// -// # AFTER: -// 'clients': -// 'persistent': -// - 'name': 'client-name' -// 'safe_search': -// 'enabled': true -// 'bing': true -// 'duckduckgo': true -// 'google': true -// 'pixabay': true -// 'yandex': true -// 'youtube': true -func upgradeSchema18to19(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 18 to 19") - diskConf["schema_version"] = 19 - - clientsVal, ok := diskConf["clients"] - if !ok { - return nil - } - - clients, ok := clientsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of clients: %T", clientsVal) - } - - persistent, ok := clients["persistent"].([]yobj) - if !ok { - return nil - } - - const safeSearchKey = "safesearch_enabled" - - for i := range persistent { - c := persistent[i] - - safeSearch := yobj{ - "enabled": true, - "bing": true, - "duckduckgo": true, - "google": true, - "pixabay": true, - "yandex": true, - "youtube": true, - } - - v, has := c[safeSearchKey] - if has { - safeSearch["enabled"] = v - } - delete(c, safeSearchKey) - - c["safe_search"] = safeSearch - } - - return nil -} - -// upgradeSchema19to20 performs the following changes: -// -// # BEFORE: -// 'statistics': -// 'interval': 1 -// -// # AFTER: -// 'statistics': -// 'interval': 24h -func upgradeSchema19to20(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 19 to 20") - diskConf["schema_version"] = 20 - - statsVal, ok := diskConf["statistics"] - if !ok { - return nil - } - - var stats yobj - stats, ok = statsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of stats: %T", statsVal) - } - - const field = "interval" - - // Set the initial value from the global configuration structure. - statsIvl := 1 - statsIvlVal, ok := stats[field] - if ok { - statsIvl, ok = statsIvlVal.(int) - if !ok { - return fmt.Errorf("unexpected type of %s: %T", field, statsIvlVal) - } - - // The initial version of upgradeSchema16to17 did not set the zero - // interval to a non-zero one. So, reset it now. - if statsIvl == 0 { - statsIvl = 1 - } - } - - stats[field] = timeutil.Duration{Duration: time.Duration(statsIvl) * timeutil.Day} - - return nil -} - -// upgradeSchema20to21 performs the following changes: -// -// # BEFORE: -// 'dns': -// 'blocked_services': -// - 'svc_name' -// -// # AFTER: -// 'dns': -// 'blocked_services': -// 'ids': -// - 'svc_name' -// 'schedule': -// 'time_zone': 'Local' -func upgradeSchema20to21(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 20 to 21") - diskConf["schema_version"] = 21 - - const field = "blocked_services" - - dnsVal, ok := diskConf["dns"] - if !ok { - return nil - } - - dns, ok := dnsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of dns: %T", dnsVal) - } - - blockedVal, ok := dns[field] - if !ok { - return nil - } - - services, ok := blockedVal.(yarr) - if !ok { - return fmt.Errorf("unexpected type of blocked: %T", blockedVal) - } - - dns[field] = yobj{ - "ids": services, - "schedule": yobj{ - "time_zone": "Local", - }, - } - - return nil -} - -// upgradeSchema21to22 performs the following changes: -// -// # BEFORE: -// 'persistent': -// - 'name': 'client_name' -// 'blocked_services': -// - 'svc_name' -// -// # AFTER: -// 'persistent': -// - 'name': 'client_name' -// 'blocked_services': -// 'ids': -// - 'svc_name' -// 'schedule': -// 'time_zone': 'Local' -func upgradeSchema21to22(diskConf yobj) (err error) { - log.Println("Upgrade yaml: 21 to 22") - diskConf["schema_version"] = 22 - - const field = "blocked_services" - - clientsVal, ok := diskConf["clients"] - if !ok { - return nil - } - - clients, ok := clientsVal.(yobj) - if !ok { - return fmt.Errorf("unexpected type of clients: %T", clientsVal) - } - - persistentVal, ok := clients["persistent"] - if !ok { - return nil - } - - persistent, ok := persistentVal.([]any) - if !ok { - return fmt.Errorf("unexpected type of persistent clients: %T", persistentVal) - } - - for i, val := range persistent { - var c yobj - c, ok = val.(yobj) - if !ok { - return fmt.Errorf("persistent client at index %d: unexpected type %T", i, val) - } - - var blockedVal any - blockedVal, ok = c[field] - if !ok { - continue - } - - var services yarr - services, ok = blockedVal.(yarr) - if !ok { - return fmt.Errorf( - "persistent client at index %d: unexpected type of blocked services: %T", - i, - blockedVal, - ) - } - - c[field] = yobj{ - "ids": services, - "schedule": yobj{ - "time_zone": "Local", - }, - } - } - - return nil -} - -// upgradeSchema22to23 performs the following changes: -// -// # BEFORE: -// 'bind_host': '1.2.3.4' -// 'bind_port': 8080 -// 'web_session_ttl': 720 -// -// # AFTER: -// 'http': -// 'address': '1.2.3.4:8080' -// 'session_ttl': '720h' -func upgradeSchema22to23(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 22 to 23") - diskConf["schema_version"] = 23 - - bindHostVal, ok := diskConf["bind_host"] - if !ok { - return nil - } - - bindHost, ok := bindHostVal.(string) - if !ok { - return fmt.Errorf("unexpected type of bind_host: %T", bindHostVal) - } - - bindHostAddr, err := netip.ParseAddr(bindHost) - if err != nil { - return fmt.Errorf("invalid bind_host value: %s", bindHost) - } - - bindPortVal, ok := diskConf["bind_port"] - if !ok { - return nil - } - - bindPort, ok := bindPortVal.(int) - if !ok { - return fmt.Errorf("unexpected type of bind_port: %T", bindPortVal) - } - - sessionTTLVal, ok := diskConf["web_session_ttl"] - if !ok { - return nil - } - - sessionTTL, ok := sessionTTLVal.(int) - if !ok { - return fmt.Errorf("unexpected type of web_session_ttl: %T", sessionTTLVal) - } - - addr := netip.AddrPortFrom(bindHostAddr, uint16(bindPort)) - if !addr.IsValid() { - return fmt.Errorf("invalid address: %s", addr) - } - - diskConf["http"] = yobj{ - "address": addr.String(), - "session_ttl": timeutil.Duration{Duration: time.Duration(sessionTTL) * time.Hour}.String(), - } - - delete(diskConf, "bind_host") - delete(diskConf, "bind_port") - delete(diskConf, "web_session_ttl") - - return nil -} - -// upgradeSchema23to24 performs the following changes: -// -// # BEFORE: -// 'log_file': "" -// 'log_max_backups': 0 -// 'log_max_size': 100 -// 'log_max_age': 3 -// 'log_compress': false -// 'log_localtime': false -// 'verbose': false -// -// # AFTER: -// 'log': -// 'file': "" -// 'max_backups': 0 -// 'max_size': 100 -// 'max_age': 3 -// 'compress': false -// 'local_time': false -// 'verbose': false -func upgradeSchema23to24(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 23 to 24") - diskConf["schema_version"] = 24 - - logObj := yobj{} - err = coalesceError( - moveField[string](diskConf, logObj, "log_file", "file"), - moveField[int](diskConf, logObj, "log_max_backups", "max_backups"), - moveField[int](diskConf, logObj, "log_max_size", "max_size"), - moveField[int](diskConf, logObj, "log_max_age", "max_age"), - moveField[bool](diskConf, logObj, "log_compress", "compress"), - moveField[bool](diskConf, logObj, "log_localtime", "local_time"), - moveField[bool](diskConf, logObj, "verbose", "verbose"), - ) - if err != nil { - // Don't wrap the error, because it's informative enough as is. - return err - } - - if len(logObj) != 0 { - diskConf["log"] = logObj - } - - delete(diskConf, "log_file") - delete(diskConf, "log_max_backups") - delete(diskConf, "log_max_size") - delete(diskConf, "log_max_age") - delete(diskConf, "log_compress") - delete(diskConf, "log_localtime") - delete(diskConf, "verbose") - - return nil -} - -// moveField gets field value for key from diskConf, and then set this value -// in newConf for newKey. -func moveField[T any](diskConf, newConf yobj, key, newKey string) (err error) { - ok, newVal, err := fieldValue[T](diskConf, key) - if !ok { - return err - } - - switch v := newVal.(type) { - case int, bool, string: - newConf[newKey] = v - default: - return fmt.Errorf("invalid type of %s: %T", key, newVal) - } - - return nil -} - -// fieldValue returns the value of type T for key in diskConf object. -func fieldValue[T any](diskConf yobj, key string) (ok bool, field any, err error) { - fieldVal, ok := diskConf[key] - if !ok { - return false, new(T), nil - } - - f, ok := fieldVal.(T) - if !ok { - return false, nil, fmt.Errorf("unexpected type of %s: %T", key, fieldVal) - } - - return true, f, nil -} - -// coalesceError returns the first non-nil error. It is named after function -// COALESCE in SQL. If all errors are nil, it returns nil. -// -// TODO(a.garipov): Consider a similar helper to group errors together to show -// as many errors as possible. -// -// TODO(a.garipov): Think of ways to merge with [aghalg.Coalesce]. -func coalesceError(errors ...error) (res error) { - for _, err := range errors { - if err != nil { - return err - } - } - - return nil -} - -// TODO(a.garipov): Replace with log.Output when we port it to our logging -// package. -func funcName() string { - pc := make([]uintptr, 10) // at least 1 entry needed - runtime.Callers(2, pc) - f := runtime.FuncForPC(pc[0]) - return path.Base(f.Name()) -} diff --git a/internal/home/web.go b/internal/home/web.go index 6649dfe2..ac659a3f 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -312,8 +312,10 @@ func (web *webAPI) mustStartHTTP3(address string) { } } -// startPprof launches the debug and profiling server on addr. -func startPprof(addr string) { +// startPprof launches the debug and profiling server on the provided port. +func startPprof(port uint16) { + addr := netip.AddrPortFrom(netutil.IPv4Localhost(), port) + runtime.SetBlockProfileRate(1) runtime.SetMutexProfileFraction(1) @@ -324,7 +326,7 @@ func startPprof(addr string) { defer log.OnPanic("pprof server") log.Info("pprof: listening on %q", addr) - err := http.ListenAndServe(addr, mux) + err := http.ListenAndServe(addr.String(), mux) if !errors.Is(err, http.ErrServerClosed) { log.Error("pprof: shutting down: %s", err) } diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go index af9a7ca4..f1ff5ad1 100644 --- a/internal/querylog/decode.go +++ b/internal/querylog/decode.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net" + "net/netip" "strings" "time" @@ -183,7 +184,11 @@ func decodeResultRuleKey(key string, i int, dec *json.Decoder, ent *logEntry) { case "IP": ent.Result.Rules, vToken = decodeVTokenAndAddRule(key, i, dec, ent.Result.Rules) if ipStr, ok := vToken.(string); ok { - ent.Result.Rules[i].IP = net.ParseIP(ipStr) + if ip, err := netip.ParseAddr(ipStr); err == nil { + ent.Result.Rules[i].IP = ip + } else { + log.Debug("querylog: decoding ipStr value: %s", err) + } } case "Text": ent.Result.Rules, vToken = decodeVTokenAndAddRule(key, i, dec, ent.Result.Rules) @@ -362,8 +367,9 @@ func decodeResultIPList(dec *json.Decoder, ent *logEntry) { return case string: - ip := net.ParseIP(v) - if ip != nil { + var ip netip.Addr + ip, err = netip.ParseAddr(v) + if err == nil { ent.Result.IPList = append(ent.Result.IPList, ip) } default: @@ -462,7 +468,7 @@ func translateResult(ent *logEntry) { resp := res.DNSRewriteResult.Response for _, ip := range res.IPList { qType := dns.TypeAAAA - if ip.To4() != nil { + if ip.Is4() { qType = dns.TypeA } diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index 3e4e4f0d..4fc1d244 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" "net" + "net/netip" "strings" "testing" "time" @@ -11,6 +12,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -71,15 +73,15 @@ func TestDecodeLogEntry(t *testing.T) { }, CanonName: "example.com", ServiceName: "example.org", - IPList: []net.IP{net.IPv4(127, 0, 0, 2)}, + IPList: []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 2})}, Rules: []*filtering.ResultRule{{ FilterListID: 42, Text: "||an.yandex.ru", - IP: net.IPv4(127, 0, 0, 2), + IP: netip.AddrFrom4([4]byte{127, 0, 0, 2}), }, { FilterListID: 43, Text: "||an2.yandex.ru", - IP: net.IPv4(127, 0, 0, 3), + IP: netip.AddrFrom4([4]byte{127, 0, 0, 3}), }}, Reason: filtering.FilteredBlockList, IsFiltered: true, @@ -192,8 +194,10 @@ func TestDecodeLogEntry(t *testing.T) { func TestDecodeLogEntry_backwardCompatability(t *testing.T) { var ( - a1, a2 = net.IP{127, 0, 0, 1}.To16(), net.IP{127, 0, 0, 2}.To16() - aaaa1, aaaa2 = net.ParseIP("::1"), net.ParseIP("::2") + a1 = netutil.IPv4Localhost() + a2 = a1.Next() + aaaa1 = netutil.IPv6Localhost() + aaaa2 = aaaa1.Next() ) testCases := []struct { @@ -230,7 +234,7 @@ func TestDecodeLogEntry_backwardCompatability(t *testing.T) { entry: `{"Result":{"IPList":["127.0.0.1","127.0.0.2","::1","::2"],"Reason":9}}`, want: &logEntry{ Result: filtering.Result{ - IPList: []net.IP{ + IPList: []netip.Addr{ a1, a2, aaaa1, diff --git a/internal/querylog/http.go b/internal/querylog/http.go index 1a29e23d..3e71af57 100644 --- a/internal/querylog/http.go +++ b/internal/querylog/http.go @@ -17,7 +17,6 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/timeutil" - "golang.org/x/exp/slices" "golang.org/x/net/idna" ) @@ -93,7 +92,7 @@ func (l *queryLog) handleQueryLog(w http.ResponseWriter, r *http.Request) { resp := entriesToJSON(entries, oldest, l.anonymizer.Load()) - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // handleQueryLogClear is the handler for the POST /control/querylog/clear HTTP @@ -118,7 +117,7 @@ func (l *queryLog) handleQueryLogInfo(w http.ResponseWriter, r *http.Request) { ivl = timeutil.Day * 90 } - _ = aghhttp.WriteJSONResponse(w, r, configJSON{ + aghhttp.WriteJSONResponseOK(w, r, configJSON{ Enabled: aghalg.BoolToNullBool(l.conf.Enabled), Interval: ivl.Hours() / 24, AnonymizeClientIP: aghalg.BoolToNullBool(l.conf.AnonymizeClientIP), @@ -141,9 +140,7 @@ func (l *queryLog) handleGetQueryLogConfig(w http.ResponseWriter, r *http.Reques } }() - slices.Sort(resp.Ignored) - - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // AnonymizeIP masks ip to anonymize the client if the ip is a valid one. @@ -224,7 +221,7 @@ func (l *queryLog) handlePutQueryLogConfig(w http.ResponseWriter, r *http.Reques return } - set, err := aghnet.NewDomainNameSet(newConf.Ignored) + engine, err := aghnet.NewIgnoreEngine(newConf.Ignored) if err != nil { aghhttp.Error(r, w, http.StatusUnprocessableEntity, "ignored: %s", err) @@ -258,7 +255,7 @@ func (l *queryLog) handlePutQueryLogConfig(w http.ResponseWriter, r *http.Reques conf := *l.conf - conf.Ignored = set + conf.Ignored = engine conf.RotationIvl = ivl conf.Enabled = newConf.Enabled == aghalg.NBTrue diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go index be9f1fee..67363510 100644 --- a/internal/querylog/qlog.go +++ b/internal/querylog/qlog.go @@ -127,7 +127,6 @@ func (l *queryLog) WriteDiskConfig(c *Config) { defer l.confMu.RUnlock() *c = *l.conf - c.Ignored = l.conf.Ignored.Clone() } // Clear memory buffer and remove log files diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index db616ba8..0b2a476b 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -5,8 +5,8 @@ import ( "net" "testing" + "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/filtering" - "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/timeutil" "github.com/miekg/dns" @@ -256,10 +256,21 @@ func TestQueryLogFileDisabled(t *testing.T) { func TestQueryLogShouldLog(t *testing.T) { const ( - ignored1 = "ignor.ed" - ignored2 = "ignored.to" + ignored1 = "ignor.ed" + ignored2 = "ignored.to" + ignoredWildcard = "*.ignored.com" + ignoredRoot = "|.^" ) - set := stringutil.NewSet(ignored1, ignored2) + + ignored := []string{ + ignored1, + ignored2, + ignoredWildcard, + ignoredRoot, + } + + engine, err := aghnet.NewIgnoreEngine(ignored) + require.NoError(t, err) findClient := func(ids []string) (c *Client, err error) { log := ids[0] == "no_log" @@ -268,7 +279,7 @@ func TestQueryLogShouldLog(t *testing.T) { } l, err := newQueryLog(Config{ - Ignored: set, + Ignored: engine, Enabled: true, RotationIvl: timeutil.Day, MemSize: 100, @@ -297,6 +308,16 @@ func TestQueryLogShouldLog(t *testing.T) { host: ignored2, ids: []string{"whatever"}, wantLog: false, + }, { + name: "no_log_ignored_wildcard", + host: "www.ignored.com", + ids: []string{"whatever"}, + wantLog: false, + }, { + name: "no_log_ignored_root", + host: ".", + ids: []string{"whatever"}, + wantLog: false, }, { name: "no_log_client_ignore", host: "example.com", diff --git a/internal/querylog/qlogreader.go b/internal/querylog/qlogreader.go index 610de02f..7d4d84b6 100644 --- a/internal/querylog/qlogreader.go +++ b/internal/querylog/qlogreader.go @@ -151,19 +151,15 @@ func (r *qLogReader) Close() error { } // closeQFiles is a helper method to close multiple qLogFile instances. -func closeQFiles(qFiles []*qLogFile) error { +func closeQFiles(qFiles []*qLogFile) (err error) { var errs []error for _, q := range qFiles { - err := q.Close() + err = q.Close() if err != nil { errs = append(errs, err) } } - if len(errs) > 0 { - return errors.List("error while closing qLogReader", errs...) - } - - return nil + return errors.Annotate(errors.Join(errs...), "closing qLogReader: %w") } diff --git a/internal/querylog/querylog.go b/internal/querylog/querylog.go index 603b6cf6..7a09f40f 100644 --- a/internal/querylog/querylog.go +++ b/internal/querylog/querylog.go @@ -11,7 +11,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/stringutil" "github.com/miekg/dns" ) @@ -36,8 +35,9 @@ type QueryLog interface { // // Do not alter any fields of this structure after using it. type Config struct { - // Ignored is the list of host names, which should not be written to log. - Ignored *stringutil.Set + // Ignored contains the list of host names, which should not be written to + // log, and matches them. + Ignored *aghnet.IgnoreEngine // Anonymizer processes the IP addresses to anonymize those if needed. Anonymizer *aghnet.IPMut diff --git a/internal/querylog/search.go b/internal/querylog/search.go index 9102c49f..d89a60d5 100644 --- a/internal/querylog/search.go +++ b/internal/querylog/search.go @@ -107,8 +107,8 @@ func (l *queryLog) search(params *searchParams) (entries []*logEntry, oldest tim // weird on the frontend. // // See https://github.com/AdguardTeam/AdGuardHome/issues/2293. - slices.SortStableFunc(entries, func(a, b *logEntry) (sortsBefore bool) { - return a.Time.After(b.Time) + slices.SortStableFunc(entries, func(a, b *logEntry) (res int) { + return -a.Time.Compare(b.Time) }) if params.offset > 0 { diff --git a/internal/schedule/schedule.go b/internal/schedule/schedule.go index 1bf96016..fe5aeb82 100644 --- a/internal/schedule/schedule.go +++ b/internal/schedule/schedule.go @@ -2,9 +2,11 @@ package schedule import ( + "encoding/json" "fmt" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/timeutil" "gopkg.in/yaml.v3" @@ -50,6 +52,10 @@ func FullWeekly() (w *Weekly) { // Clone returns a deep copy of a weekly. func (w *Weekly) Clone() (c *Weekly) { + if w == nil { + return nil + } + // NOTE: Do not use time.LoadLocation, because the results will be // different on time zone database update. return &Weekly{ @@ -75,12 +81,62 @@ func (w *Weekly) Contains(t time.Time) (ok bool) { return dr.contains(offset) } +// type check +var _ json.Unmarshaler = (*Weekly)(nil) + +// UnmarshalJSON implements the [json.Unmarshaler] interface for *Weekly. +func (w *Weekly) UnmarshalJSON(data []byte) (err error) { + conf := &weeklyConfigJSON{} + err = json.Unmarshal(data, conf) + if err != nil { + return err + } + + weekly := Weekly{} + + weekly.location, err = time.LoadLocation(conf.TimeZone) + if err != nil { + return err + } + + days := []*dayConfigJSON{ + time.Sunday: conf.Sunday, + time.Monday: conf.Monday, + time.Tuesday: conf.Tuesday, + time.Wednesday: conf.Wednesday, + time.Thursday: conf.Thursday, + time.Friday: conf.Friday, + time.Saturday: conf.Saturday, + } + for i, d := range days { + var r dayRange + + if d != nil { + r = dayRange{ + start: time.Duration(d.Start), + end: time.Duration(d.End), + } + } + + err = w.validate(r) + if err != nil { + return fmt.Errorf("weekday %s: %w", time.Weekday(i), err) + } + + weekly.days[i] = r + } + + *w = weekly + + return nil +} + // type check var _ yaml.Unmarshaler = (*Weekly)(nil) // UnmarshalYAML implements the [yaml.Unmarshaler] interface for *Weekly. func (w *Weekly) UnmarshalYAML(value *yaml.Node) (err error) { - conf := &weeklyConfig{} + conf := &weeklyConfigYAML{} err = value.Decode(conf) if err != nil { @@ -96,7 +152,7 @@ func (w *Weekly) UnmarshalYAML(value *yaml.Node) (err error) { return err } - days := []dayConfig{ + days := []dayConfigYAML{ time.Sunday: conf.Sunday, time.Monday: conf.Monday, time.Tuesday: conf.Tuesday, @@ -124,24 +180,24 @@ func (w *Weekly) UnmarshalYAML(value *yaml.Node) (err error) { return nil } -// weeklyConfig is the YAML configuration structure of Weekly. -type weeklyConfig struct { +// weeklyConfigYAML is the YAML configuration structure of Weekly. +type weeklyConfigYAML struct { // TimeZone is the local time zone. TimeZone string `yaml:"time_zone"` // Days of the week. - Sunday dayConfig `yaml:"sun,omitempty"` - Monday dayConfig `yaml:"mon,omitempty"` - Tuesday dayConfig `yaml:"tue,omitempty"` - Wednesday dayConfig `yaml:"wed,omitempty"` - Thursday dayConfig `yaml:"thu,omitempty"` - Friday dayConfig `yaml:"fri,omitempty"` - Saturday dayConfig `yaml:"sat,omitempty"` + Sunday dayConfigYAML `yaml:"sun,omitempty"` + Monday dayConfigYAML `yaml:"mon,omitempty"` + Tuesday dayConfigYAML `yaml:"tue,omitempty"` + Wednesday dayConfigYAML `yaml:"wed,omitempty"` + Thursday dayConfigYAML `yaml:"thu,omitempty"` + Friday dayConfigYAML `yaml:"fri,omitempty"` + Saturday dayConfigYAML `yaml:"sat,omitempty"` } -// dayConfig is the YAML configuration structure of dayRange. -type dayConfig struct { +// dayConfigYAML is the YAML configuration structure of dayRange. +type dayConfigYAML struct { Start timeutil.Duration `yaml:"start"` End timeutil.Duration `yaml:"end"` } @@ -172,38 +228,57 @@ func (w *Weekly) validate(r dayRange) (err error) { } } +// type check +var _ json.Marshaler = (*Weekly)(nil) + +// MarshalJSON implements the [json.Marshaler] interface for *Weekly. +func (w *Weekly) MarshalJSON() (data []byte, err error) { + c := &weeklyConfigJSON{ + TimeZone: w.location.String(), + Sunday: w.days[time.Sunday].toDayConfigJSON(), + Monday: w.days[time.Monday].toDayConfigJSON(), + Tuesday: w.days[time.Tuesday].toDayConfigJSON(), + Wednesday: w.days[time.Wednesday].toDayConfigJSON(), + Thursday: w.days[time.Thursday].toDayConfigJSON(), + Friday: w.days[time.Friday].toDayConfigJSON(), + Saturday: w.days[time.Saturday].toDayConfigJSON(), + } + + return json.Marshal(c) +} + // type check var _ yaml.Marshaler = (*Weekly)(nil) // MarshalYAML implements the [yaml.Marshaler] interface for *Weekly. func (w *Weekly) MarshalYAML() (v any, err error) { - return weeklyConfig{ + return weeklyConfigYAML{ TimeZone: w.location.String(), - Sunday: dayConfig{ + Sunday: dayConfigYAML{ Start: timeutil.Duration{Duration: w.days[time.Sunday].start}, End: timeutil.Duration{Duration: w.days[time.Sunday].end}, }, - Monday: dayConfig{ + Monday: dayConfigYAML{ Start: timeutil.Duration{Duration: w.days[time.Monday].start}, End: timeutil.Duration{Duration: w.days[time.Monday].end}, }, - Tuesday: dayConfig{ + Tuesday: dayConfigYAML{ Start: timeutil.Duration{Duration: w.days[time.Tuesday].start}, End: timeutil.Duration{Duration: w.days[time.Tuesday].end}, }, - Wednesday: dayConfig{ + Wednesday: dayConfigYAML{ Start: timeutil.Duration{Duration: w.days[time.Wednesday].start}, End: timeutil.Duration{Duration: w.days[time.Wednesday].end}, }, - Thursday: dayConfig{ + Thursday: dayConfigYAML{ Start: timeutil.Duration{Duration: w.days[time.Thursday].start}, End: timeutil.Duration{Duration: w.days[time.Thursday].end}, }, - Friday: dayConfig{ + Friday: dayConfigYAML{ Start: timeutil.Duration{Duration: w.days[time.Friday].start}, End: timeutil.Duration{Duration: w.days[time.Friday].end}, }, - Saturday: dayConfig{ + Saturday: dayConfigYAML{ Start: timeutil.Duration{Duration: w.days[time.Saturday].start}, End: timeutil.Duration{Duration: w.days[time.Saturday].end}, }, @@ -248,3 +323,38 @@ func (r dayRange) validate() (err error) { func (r *dayRange) contains(offset time.Duration) (ok bool) { return r.start <= offset && offset < r.end } + +// toDayConfigJSON returns nil if the day range is empty, otherwise returns +// initialized JSON configuration of the day range. +func (r dayRange) toDayConfigJSON() (j *dayConfigJSON) { + if (r == dayRange{}) { + return nil + } + + return &dayConfigJSON{ + Start: aghhttp.JSONDuration(r.start), + End: aghhttp.JSONDuration(r.end), + } +} + +// weeklyConfigJSON is the JSON configuration structure of Weekly. +type weeklyConfigJSON struct { + // TimeZone is the local time zone. + TimeZone string `json:"time_zone"` + + // Days of the week. + + Sunday *dayConfigJSON `json:"sun,omitempty"` + Monday *dayConfigJSON `json:"mon,omitempty"` + Tuesday *dayConfigJSON `json:"tue,omitempty"` + Wednesday *dayConfigJSON `json:"wed,omitempty"` + Thursday *dayConfigJSON `json:"thu,omitempty"` + Friday *dayConfigJSON `json:"fri,omitempty"` + Saturday *dayConfigJSON `json:"sat,omitempty"` +} + +// dayConfigJSON is the JSON configuration structure of dayRange. +type dayConfigJSON struct { + Start aghhttp.JSONDuration `json:"start"` + End aghhttp.JSONDuration `json:"end"` +} diff --git a/internal/schedule/schedule_internal_test.go b/internal/schedule/schedule_internal_test.go index f500524e..0d9aa705 100644 --- a/internal/schedule/schedule_internal_test.go +++ b/internal/schedule/schedule_internal_test.go @@ -1,6 +1,7 @@ package schedule import ( + "encoding/json" "testing" "time" @@ -122,7 +123,7 @@ func TestWeekly_Contains(t *testing.T) { } } -const brusselsSunday = ` +const brusselsSundayYAML = ` sun: start: 12h end: 14h @@ -179,7 +180,7 @@ yaml: "bad" }, { name: "brussels_sunday", wantErrMsg: "", - data: []byte(brusselsSunday), + data: []byte(brusselsSundayYAML), want: brusselsWeekly, }, { name: "start_equal_end", @@ -240,7 +241,7 @@ func TestWeekly_MarshalYAML(t *testing.T) { want: &Weekly{}, }, { name: "brussels_sunday", - data: []byte(brusselsSunday), + data: []byte(brusselsSundayYAML), want: brusselsWeekly, }} @@ -369,3 +370,142 @@ func TestDayRange_Validate(t *testing.T) { }) } } + +const brusselsSundayJSON = `{ + "sun": { + "end": 50400000, + "start": 43200000 + }, + "time_zone": "Europe/Brussels" +}` + +func TestWeekly_UnmarshalJSON(t *testing.T) { + const ( + sameTime = `{ + "sun": { + "end": 32400000, + "start": 32400000 + } +}` + negativeStart = `{ + "sun": { + "end": 3600000, + "start": -3600000 + } +}` + badTZ = `{ + "time_zone": "bad_timezone" +}` + badJSON = `{ + "bad": "json", +}` + ) + + brusseltsTZ, err := time.LoadLocation("Europe/Brussels") + require.NoError(t, err) + + brusselsWeekly := &Weekly{ + days: [7]dayRange{{ + start: time.Hour * 12, + end: time.Hour * 14, + }}, + location: brusseltsTZ, + } + + testCases := []struct { + name string + wantErrMsg string + data []byte + want *Weekly + }{{ + name: "empty", + wantErrMsg: "unexpected end of JSON input", + data: []byte(""), + want: &Weekly{}, + }, { + name: "null", + wantErrMsg: "", + data: []byte("null"), + want: &Weekly{location: time.UTC}, + }, { + name: "brussels_sunday", + wantErrMsg: "", + data: []byte(brusselsSundayJSON), + want: brusselsWeekly, + }, { + name: "start_equal_end", + wantErrMsg: "weekday Sunday: bad day range: start 9h0m0s is greater or equal to end 9h0m0s", + data: []byte(sameTime), + want: &Weekly{}, + }, { + name: "start_negative", + wantErrMsg: "weekday Sunday: bad day range: start -1h0m0s is negative", + data: []byte(negativeStart), + want: &Weekly{}, + }, { + name: "bad_time_zone", + wantErrMsg: "unknown time zone bad_timezone", + data: []byte(badTZ), + want: &Weekly{}, + }, { + name: "bad_json", + wantErrMsg: "invalid character '}' looking for beginning of object key string", + data: []byte(badJSON), + want: &Weekly{}, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + w := &Weekly{} + err = json.Unmarshal(tc.data, w) + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + + assert.Equal(t, tc.want, w) + }) + } +} + +func TestWeekly_MarshalJSON(t *testing.T) { + brusselsTZ, err := time.LoadLocation("Europe/Brussels") + require.NoError(t, err) + + brusselsWeekly := &Weekly{ + days: [7]dayRange{time.Sunday: { + start: time.Hour * 12, + end: time.Hour * 14, + }}, + location: brusselsTZ, + } + + testCases := []struct { + name string + data []byte + want *Weekly + }{{ + name: "empty", + data: []byte(""), + want: &Weekly{}, + }, { + name: "null", + data: []byte("null"), + want: &Weekly{}, + }, { + name: "brussels_sunday", + data: []byte(brusselsSundayJSON), + want: brusselsWeekly, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var data []byte + data, err = json.Marshal(brusselsWeekly) + require.NoError(t, err) + + w := &Weekly{} + err = json.Unmarshal(data, w) + require.NoError(t, err) + + assert.Equal(t, brusselsWeekly, w) + }) + } +} diff --git a/internal/stats/http.go b/internal/stats/http.go index 38f6be55..faec0d14 100644 --- a/internal/stats/http.go +++ b/internal/stats/http.go @@ -12,13 +12,15 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/timeutil" - "golang.org/x/exp/slices" ) // topAddrs is an alias for the types of the TopFoo fields of statsResponse. // The key is either a client's address or a requested address. type topAddrs = map[string]uint64 +// topAddrsFloat is like [topAddrs] but the value is float64 number. +type topAddrsFloat = map[string]float64 + // StatsResp is a response to the GET /control/stats. type StatsResp struct { TimeUnits string `json:"time_units"` @@ -27,6 +29,9 @@ type StatsResp struct { TopClients []topAddrs `json:"top_clients"` TopBlocked []topAddrs `json:"top_blocked_domains"` + TopUpstreamsResponses []topAddrs `json:"top_upstreams_responses"` + TopUpstreamsAvgTime []topAddrsFloat `json:"top_upstreams_avg_time"` + DNSQueries []uint64 `json:"dns_queries"` BlockedFiltering []uint64 `json:"blocked_filtering"` @@ -47,7 +52,7 @@ func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) { start := time.Now() var ( - resp StatsResp + resp *StatsResp ok bool ) func() { @@ -67,7 +72,7 @@ func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) { return } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // configResp is the response to the GET /control/stats_info. @@ -116,7 +121,7 @@ func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) { resp.IntervalDays = 0 } - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // handleGetStatsConfig is the handler for the GET /control/stats/config HTTP @@ -134,9 +139,7 @@ func (s *StatsCtx) handleGetStatsConfig(w http.ResponseWriter, r *http.Request) } }() - slices.Sort(resp.Ignored) - - _ = aghhttp.WriteJSONResponse(w, r, resp) + aghhttp.WriteJSONResponseOK(w, r, resp) } // handleStatsConfig is the handler for the POST /control/stats_config HTTP API. @@ -178,7 +181,7 @@ func (s *StatsCtx) handlePutStatsConfig(w http.ResponseWriter, r *http.Request) return } - set, err := aghnet.NewDomainNameSet(reqData.Ignored) + engine, err := aghnet.NewIgnoreEngine(reqData.Ignored) if err != nil { aghhttp.Error(r, w, http.StatusUnprocessableEntity, "ignored: %s", err) @@ -204,7 +207,7 @@ func (s *StatsCtx) handlePutStatsConfig(w http.ResponseWriter, r *http.Request) s.confMu.Lock() defer s.confMu.Unlock() - s.ignored = set + s.ignored = engine s.limit = ivl s.enabled = reqData.Enabled == aghalg.NBTrue } diff --git a/internal/stats/http_test.go b/internal/stats/http_test.go index 69e75d6b..7e358a03 100644 --- a/internal/stats/http_test.go +++ b/internal/stats/http_test.go @@ -75,29 +75,6 @@ func TestHandleStatsConfig(t *testing.T) { }, wantCode: http.StatusOK, wantErr: "", - }, { - name: "ignored_duplicate", - body: getConfigResp{ - Enabled: aghalg.NBTrue, - Interval: float64(minIvl.Milliseconds()), - Ignored: []string{ - "ignor.ed", - "ignor.ed", - }, - }, - wantCode: http.StatusUnprocessableEntity, - wantErr: "ignored: duplicate hostname \"ignor.ed\" at index 1\n", - }, { - name: "ignored_empty", - body: getConfigResp{ - Enabled: aghalg.NBTrue, - Interval: float64(minIvl.Milliseconds()), - Ignored: []string{ - "", - }, - }, - wantCode: http.StatusUnprocessableEntity, - wantErr: "ignored: at index 0: hostname is empty\n", }, { name: "enabled_is_null", body: getConfigResp{ diff --git a/internal/stats/stats.go b/internal/stats/stats.go index 851aea98..e9533ca2 100644 --- a/internal/stats/stats.go +++ b/internal/stats/stats.go @@ -5,7 +5,6 @@ package stats import ( "fmt" "io" - "net" "net/netip" "os" "sync" @@ -13,9 +12,9 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" + "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" - "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/timeutil" "go.etcd.io/bbolt" ) @@ -59,8 +58,9 @@ type Config struct { // endpoints. HTTPRegister aghhttp.RegisterFunc - // Ignored is the list of host names, which should not be counted. - Ignored *stringutil.Set + // Ignored contains the list of host names, which should not be counted, + // and matches them. + Ignored *aghnet.IgnoreEngine // Filename is the name of the database file. Filename string @@ -80,7 +80,7 @@ type Interface interface { io.Closer // Update collects the incoming statistics data. - Update(e Entry) + Update(e *Entry) // GetTopClientIP returns at most limit IP addresses corresponding to the // clients with the most number of requests. @@ -118,8 +118,9 @@ type StatsCtx struct { // confMu protects ignored, limit, and enabled. confMu *sync.RWMutex - // ignored is the list of host names, which should not be counted. - ignored *stringutil.Set + // ignored contains the list of host names, which should not be counted, + // and matches them. + ignored *aghnet.IgnoreEngine // shouldCountClient returns client's ignore setting. shouldCountClient func([]string) bool @@ -225,7 +226,7 @@ func (s *StatsCtx) Start() { go s.periodicFlush() } -// Close implements the io.Closer interface for *StatsCtx. +// Close implements the [io.Closer] interface for *StatsCtx. func (s *StatsCtx) Close() (err error) { defer func() { err = errors.Annotate(err, "stats: closing: %w") }() @@ -256,8 +257,9 @@ func (s *StatsCtx) Close() (err error) { return udb.flushUnitToDB(tx, s.curr.id) } -// Update implements the Interface interface for *StatsCtx. -func (s *StatsCtx) Update(e Entry) { +// Update implements the [Interface] interface for *StatsCtx. e must not be +// nil. +func (s *StatsCtx) Update(e *Entry) { s.confMu.Lock() defer s.confMu.Unlock() @@ -265,8 +267,9 @@ func (s *StatsCtx) Update(e Entry) { return } - if e.Result == 0 || e.Result >= resultLast || e.Domain == "" || e.Client == "" { - log.Debug("stats: malformed entry") + err := e.validate() + if err != nil { + log.Debug("stats: updating: validating entry: %s", err) return } @@ -280,20 +283,15 @@ func (s *StatsCtx) Update(e Entry) { return } - clientID := e.Client - if ip := net.ParseIP(clientID); ip != nil { - clientID = ip.String() - } - - s.curr.add(e.Result, e.Domain, clientID, uint64(e.Time)) + s.curr.add(e) } -// WriteDiskConfig implements the Interface interface for *StatsCtx. +// WriteDiskConfig implements the [Interface] interface for *StatsCtx. func (s *StatsCtx) WriteDiskConfig(dc *Config) { s.confMu.RLock() defer s.confMu.RUnlock() - dc.Ignored = s.ignored.Clone() + dc.Ignored = s.ignored dc.Limit = s.limit dc.Enabled = s.enabled } @@ -412,6 +410,12 @@ func (s *StatsCtx) flush() (cont bool, sleepFor time.Duration) { return true, time.Second } + return s.flushDB(id, limit, ptr) +} + +// flushDB flushes the unit to the database. confMu and currMu are expected to +// be locked. +func (s *StatsCtx) flushDB(id, limit uint32, ptr *unit) (cont bool, sleepFor time.Duration) { db := s.db.Load() if db == nil { return true, 0 @@ -533,7 +537,8 @@ func (s *StatsCtx) clear() (err error) { return nil } -func (s *StatsCtx) loadUnits(limit uint32) (units []*unitDB, firstID uint32) { +// loadUnits returns stored units from the database and current unit ID. +func (s *StatsCtx) loadUnits(limit uint32) (units []*unitDB, curID uint32) { db := s.db.Load() if db == nil { return nil, 0 @@ -553,7 +558,6 @@ func (s *StatsCtx) loadUnits(limit uint32) (units []*unitDB, firstID uint32) { cur := s.curr - var curID uint32 if cur != nil { curID = cur.id } else { @@ -562,7 +566,7 @@ func (s *StatsCtx) loadUnits(limit uint32) (units []*unitDB, firstID uint32) { // Per-hour units. units = make([]*unitDB, 0, limit) - firstID = curID - limit + 1 + firstID := curID - limit + 1 for i := firstID; i != curID; i++ { u := loadUnitFromDB(tx, i) if u == nil { @@ -584,7 +588,7 @@ func (s *StatsCtx) loadUnits(limit uint32) (units []*unitDB, firstID uint32) { log.Fatalf("loaded %d units whilst the desired number is %d", unitsLen, limit) } - return units, firstID + return units, curID } // ShouldCount returns true if request for the host should be counted. diff --git a/internal/stats/stats_internal_test.go b/internal/stats/stats_internal_test.go index 9a173f65..9fb6b526 100644 --- a/internal/stats/stats_internal_test.go +++ b/internal/stats/stats_internal_test.go @@ -14,24 +14,6 @@ import ( "github.com/stretchr/testify/require" ) -// TODO(e.burkov): Use more realistic data. -func TestStatsCollector(t *testing.T) { - ng := func(_ *unitDB) uint64 { return 0 } - units := make([]*unitDB, 720) - - t.Run("hours", func(t *testing.T) { - statsData := statsCollector(units, 0, Hours, ng) - assert.Len(t, statsData, 720) - }) - - t.Run("days", func(t *testing.T) { - for i := 0; i != 25; i++ { - statsData := statsCollector(units, uint32(i), Days, ng) - require.Lenf(t, statsData, 30, "i=%d", i) - } - }) -} - func TestStats_races(t *testing.T) { var r uint32 idGen := func() (id uint32) { return atomic.LoadUint32(&r) } @@ -50,11 +32,11 @@ func TestStats_races(t *testing.T) { testutil.CleanupAndRequireSuccess(t, s.Close) writeFunc := func(start, fin *sync.WaitGroup, waitCh <-chan unit, i int) { - e := Entry{ + e := &Entry{ Domain: fmt.Sprintf("example-%d.org", i), Client: fmt.Sprintf("client_%d", i), Result: Result(i)%(resultLast-1) + 1, - Time: uint32(time.Since(startTime).Milliseconds()), + Time: time.Since(startTime), } start.Done() @@ -103,3 +85,86 @@ func TestStats_races(t *testing.T) { finWG.Wait() } } + +func TestStatsCtx_FillCollectedStats_daily(t *testing.T) { + const ( + daysCount = 10 + + timeUnits = "days" + ) + + s, err := New(Config{ + ShouldCountClient: func([]string) bool { return true }, + Filename: filepath.Join(t.TempDir(), "./stats.db"), + Limit: time.Hour, + }) + require.NoError(t, err) + + testutil.CleanupAndRequireSuccess(t, s.Close) + + sum := make([][]uint64, resultLast) + sum[RFiltered] = make([]uint64, daysCount) + sum[RSafeBrowsing] = make([]uint64, daysCount) + sum[RParental] = make([]uint64, daysCount) + + total := make([]uint64, daysCount) + + dailyData := []*unitDB{} + + for i := 0; i < daysCount*24; i++ { + n := uint64(i) + nResult := make([]uint64, resultLast) + nResult[RFiltered] = n + nResult[RSafeBrowsing] = n + nResult[RParental] = n + + day := i / 24 + sum[RFiltered][day] += n + sum[RSafeBrowsing][day] += n + sum[RParental][day] += n + + t := n * 3 + + total[day] += t + + dailyData = append(dailyData, &unitDB{ + NTotal: t, + NResult: nResult, + }) + } + + data := &StatsResp{} + + // In this way we will not skip first hours. + curID := uint32(daysCount * 24) + + s.fillCollectedStats(data, dailyData, curID) + + assert.Equal(t, timeUnits, data.TimeUnits) + assert.Equal(t, sum[RFiltered], data.BlockedFiltering) + assert.Equal(t, sum[RSafeBrowsing], data.ReplacedSafebrowsing) + assert.Equal(t, sum[RParental], data.ReplacedParental) + assert.Equal(t, total, data.DNSQueries) +} + +func TestStatsCtx_DataFromUnits_month(t *testing.T) { + const hoursInMonth = 720 + + s, err := New(Config{ + ShouldCountClient: func([]string) bool { return true }, + Filename: filepath.Join(t.TempDir(), "./stats.db"), + Limit: time.Hour, + }) + require.NoError(t, err) + + testutil.CleanupAndRequireSuccess(t, s.Close) + + units, curID := s.loadUnits(hoursInMonth) + require.Len(t, units, hoursInMonth) + + var h uint32 + for h = 1; h <= hoursInMonth; h++ { + data := s.dataFromUnits(units[:h], curID) + require.NotNil(t, data) + } +} diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index 37b82163..004e9513 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -9,10 +9,11 @@ import ( "path/filepath" "sync/atomic" "testing" + "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/stats" "github.com/AdguardTeam/golibs/netutil" - "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/timeutil" "github.com/miekg/dns" @@ -72,24 +73,29 @@ func TestStats(t *testing.T) { t.Run("data", func(t *testing.T) { const reqDomain = "domain" + const respUpstream = "upstream" - entries := []stats.Entry{{ - Domain: reqDomain, - Client: cliIPStr, - Result: stats.RFiltered, - Time: 123456, + entries := []*stats.Entry{{ + Domain: reqDomain, + Client: cliIPStr, + Result: stats.RFiltered, + Time: time.Microsecond * 123456, + Upstream: respUpstream, }, { - Domain: reqDomain, - Client: cliIPStr, - Result: stats.RNotFiltered, - Time: 123456, + Domain: reqDomain, + Client: cliIPStr, + Result: stats.RNotFiltered, + Time: time.Microsecond * 123456, + Upstream: respUpstream, }} wantData := &stats.StatsResp{ - TimeUnits: "hours", - TopQueried: []map[string]uint64{0: {reqDomain: 1}}, - TopClients: []map[string]uint64{0: {cliIPStr: 2}}, - TopBlocked: []map[string]uint64{0: {reqDomain: 1}}, + TimeUnits: "hours", + TopQueried: []map[string]uint64{0: {reqDomain: 1}}, + TopClients: []map[string]uint64{0: {cliIPStr: 2}}, + TopBlocked: []map[string]uint64{0: {reqDomain: 1}}, + TopUpstreamsResponses: []map[string]uint64{0: {respUpstream: 2}}, + TopUpstreamsAvgTime: []map[string]float64{0: {respUpstream: 0.123456}}, DNSQueries: []uint64{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, @@ -138,14 +144,16 @@ func TestStats(t *testing.T) { _24zeroes := [24]uint64{} emptyData := &stats.StatsResp{ - TimeUnits: "hours", - TopQueried: []map[string]uint64{}, - TopClients: []map[string]uint64{}, - TopBlocked: []map[string]uint64{}, - DNSQueries: _24zeroes[:], - BlockedFiltering: _24zeroes[:], - ReplacedSafebrowsing: _24zeroes[:], - ReplacedParental: _24zeroes[:], + TimeUnits: "hours", + TopQueried: []map[string]uint64{}, + TopClients: []map[string]uint64{}, + TopBlocked: []map[string]uint64{}, + TopUpstreamsResponses: []map[string]uint64{}, + TopUpstreamsAvgTime: []map[string]float64{}, + DNSQueries: _24zeroes[:], + BlockedFiltering: _24zeroes[:], + ReplacedSafebrowsing: _24zeroes[:], + ReplacedParental: _24zeroes[:], } req = httptest.NewRequest(http.MethodGet, "/control/stats", nil) @@ -187,7 +195,7 @@ func TestLargeNumbers(t *testing.T) { for i := 0; i < cliNumPerHour; i++ { ip := net.IP{127, 0, byte((i & 0xff00) >> 8), byte(i & 0xff)} - e := stats.Entry{ + e := &stats.Entry{ Domain: fmt.Sprintf("domain%d.hour%d", i, h), Client: ip.String(), Result: stats.RNotFiltered, @@ -207,13 +215,15 @@ func TestShouldCount(t *testing.T) { ignored1 = "ignor.ed" ignored2 = "ignored.to" ) - set := stringutil.NewSet(ignored1, ignored2) + ignored := []string{ignored1, ignored2} + engine, err := aghnet.NewIgnoreEngine(ignored) + require.NoError(t, err) s, err := stats.New(stats.Config{ Enabled: true, Filename: filepath.Join(t.TempDir(), "stats.db"), Limit: timeutil.Day, - Ignored: set, + Ignored: engine, ShouldCountClient: func(ids []string) (a bool) { return ids[0] != "no_count" }, diff --git a/internal/stats/unit.go b/internal/stats/unit.go index 8de01aa0..31fc3984 100644 --- a/internal/stats/unit.go +++ b/internal/stats/unit.go @@ -7,34 +7,33 @@ import ( "fmt" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" - "github.com/AdguardTeam/golibs/stringutil" "go.etcd.io/bbolt" + "golang.org/x/exp/maps" "golang.org/x/exp/slices" ) -// TODO(a.garipov): Rewrite all of this. Add proper error handling and -// inspection. Improve logging. Decrease complexity. - const ( // maxDomains is the max number of top domains to return. maxDomains = 100 + // maxClients is the max number of top clients to return. maxClients = 100 + + // maxUpstreams is the max number of top upstreams to return. + maxUpstreams = 100 ) // UnitIDGenFunc is the signature of a function that generates a unique ID for // the statistics unit. type UnitIDGenFunc func() (id uint32) -// TimeUnit is the unit of measuring time while aggregating the statistics. -type TimeUnit int - -// Supported TimeUnit values. +// Supported values of [StatsResp.TimeUnits]. const ( - Hours TimeUnit = iota - Days + timeUnitsHours = "hours" + timeUnitsDays = "days" ) // Result is the resulting code of processing the DNS request. @@ -63,11 +62,30 @@ type Entry struct { // Domain is the domain name requested. Domain string + // Upstream is the upstream DNS server. + Upstream string + // Result is the result of processing the request. Result Result - // Time is the duration of the request processing in milliseconds. - Time uint32 + // Time is the duration of the request processing. + Time time.Duration +} + +// validate returs an error if entry is not valid. +func (e *Entry) validate() (err error) { + switch { + case e.Result == 0: + return errors.Error("result code is not set") + case e.Result >= resultLast: + return fmt.Errorf("unknown result code %d", e.Result) + case e.Domain == "": + return errors.Error("domain is empty") + case e.Client == "": + return errors.Error("client is empty") + default: + return nil + } } // unit collects the statistics data for a specific period of time. @@ -82,6 +100,13 @@ type unit struct { // clients stores the number of requests from each client. clients map[string]uint64 + // upstreamsResponses stores the number of responses from each upstream. + upstreamsResponses map[string]uint64 + + // upstreamsTimeSum stores the sum of processing time in microseconds of + // responses from each upstream. + upstreamsTimeSum map[string]uint64 + // nResult stores the number of requests grouped by it's result. nResult []uint64 @@ -95,7 +120,7 @@ type unit struct { // nTotal stores the total number of requests. nTotal uint64 - // timeSum stores the sum of processing time in milliseconds of each request + // timeSum stores the sum of processing time in microseconds of each request // written by the unit. timeSum uint64 } @@ -103,11 +128,13 @@ type unit struct { // newUnit allocates the new *unit. func newUnit(id uint32) (u *unit) { return &unit{ - domains: map[string]uint64{}, - blockedDomains: map[string]uint64{}, - clients: map[string]uint64{}, - nResult: make([]uint64, resultLast), - id: id, + domains: map[string]uint64{}, + blockedDomains: map[string]uint64{}, + clients: map[string]uint64{}, + upstreamsResponses: map[string]uint64{}, + upstreamsTimeSum: map[string]uint64{}, + nResult: make([]uint64, resultLast), + id: id, } } @@ -135,10 +162,17 @@ type unitDB struct { // Clients is the number of requests from each client. Clients []countPair + // UpstreamsResponses is the number of responses from each upstream. + UpstreamsResponses []countPair + + // UpstreamsTimeSum is the sum of processing time in microseconds of + // responses from each upstream. + UpstreamsTimeSum []countPair + // NTotal is the total number of requests. NTotal uint64 - // TimeAvg is the average of processing times in milliseconds of all the + // TimeAvg is the average of processing times in microseconds of all the // requests in the unit. TimeAvg uint32 } @@ -184,15 +218,25 @@ func unitNameToID(name []byte) (id uint32, ok bool) { return uint32(binary.BigEndian.Uint64(name)), true } +// compareCount used to sort countPair by Count in descending order. +func (a countPair) compareCount(b countPair) (res int) { + switch x, y := a.Count, b.Count; { + case x > y: + return -1 + case x < y: + return +1 + default: + return 0 + } +} + func convertMapToSlice(m map[string]uint64, max int) (s []countPair) { s = make([]countPair, 0, len(m)) for k, v := range m { s = append(s, countPair{Name: k, Count: v}) } - slices.SortFunc(s, func(a, b countPair) (sortsBefore bool) { - return a.Count > b.Count - }) + slices.SortFunc(s, countPair.compareCount) if max > len(s) { max = len(s) } @@ -218,12 +262,14 @@ func (u *unit) serialize() (udb *unitDB) { } return &unitDB{ - NTotal: u.nTotal, - NResult: append([]uint64{}, u.nResult...), - Domains: convertMapToSlice(u.domains, maxDomains), - BlockedDomains: convertMapToSlice(u.blockedDomains, maxDomains), - Clients: convertMapToSlice(u.clients, maxClients), - TimeAvg: timeAvg, + NTotal: u.nTotal, + NResult: append([]uint64{}, u.nResult...), + Domains: convertMapToSlice(u.domains, maxDomains), + BlockedDomains: convertMapToSlice(u.blockedDomains, maxDomains), + Clients: convertMapToSlice(u.clients, maxClients), + UpstreamsResponses: convertMapToSlice(u.upstreamsResponses, maxUpstreams), + UpstreamsTimeSum: convertMapToSlice(u.upstreamsTimeSum, maxUpstreams), + TimeAvg: timeAvg, } } @@ -262,21 +308,29 @@ func (u *unit) deserialize(udb *unitDB) { u.domains = convertSliceToMap(udb.Domains) u.blockedDomains = convertSliceToMap(udb.BlockedDomains) u.clients = convertSliceToMap(udb.Clients) + u.upstreamsResponses = convertSliceToMap(udb.UpstreamsResponses) + u.upstreamsTimeSum = convertSliceToMap(udb.UpstreamsTimeSum) u.timeSum = uint64(udb.TimeAvg) * udb.NTotal } // add adds new data to u. It's safe for concurrent use. -func (u *unit) add(res Result, domain, cli string, dur uint64) { - u.nResult[res]++ - if res == RNotFiltered { - u.domains[domain]++ +func (u *unit) add(e *Entry) { + u.nResult[e.Result]++ + if e.Result == RNotFiltered { + u.domains[e.Domain]++ } else { - u.blockedDomains[domain]++ + u.blockedDomains[e.Domain]++ } - u.clients[cli]++ - u.timeSum += dur + u.clients[e.Client]++ + t := uint64(e.Time.Microseconds()) + u.timeSum += t u.nTotal++ + + if e.Upstream != "" { + u.upstreamsResponses[e.Upstream]++ + u.upstreamsTimeSum[e.Upstream] += t + } } // flushUnitToDB puts udb to the database at id. @@ -311,48 +365,12 @@ func convertTopSlice(a []countPair) (m []map[string]uint64) { return m } -// numsGetter is a signature for statsCollector argument. -type numsGetter func(u *unitDB) (num uint64) - -// statsCollector collects statisctics for the given *unitDB slice by specified -// timeUnit using ng to retrieve data. -func statsCollector(units []*unitDB, firstID uint32, timeUnit TimeUnit, ng numsGetter) (nums []uint64) { - if timeUnit == Hours { - nums = make([]uint64, 0, len(units)) - for _, u := range units { - nums = append(nums, ng(u)) - } - } else { - // Per time unit counters: 720 hours may span 31 days, so we - // skip data for the first day in this case. - // align_ceil(24) - firstDayID := (firstID + 24 - 1) / 24 * 24 - - var sum uint64 - id := firstDayID - nextDayID := firstDayID + 24 - for i := int(firstDayID - firstID); i != len(units); i++ { - sum += ng(units[i]) - if id == nextDayID { - nums = append(nums, sum) - sum = 0 - nextDayID += 24 - } - id++ - } - if id <= nextDayID { - nums = append(nums, sum) - } - } - return nums -} - // pairsGetter is a signature for topsCollector argument. type pairsGetter func(u *unitDB) (pairs []countPair) // topsCollector collects statistics about highest values from the given *unitDB // slice using pg to retrieve data. -func topsCollector(units []*unitDB, max int, ignored *stringutil.Set, pg pairsGetter) []map[string]uint64 { +func topsCollector(units []*unitDB, max int, ignored *aghnet.IgnoreEngine, pg pairsGetter) []map[string]uint64 { m := map[string]uint64{} for _, u := range units { for _, cp := range pg(u) { @@ -385,14 +403,16 @@ func topsCollector(units []*unitDB, max int, ignored *stringutil.Set, pg pairsGe // // The total counters (DNS queries, blocked, etc.) are just the sum of data // for all units. -func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) { +func (s *StatsCtx) getData(limit uint32) (resp *StatsResp, ok bool) { if limit == 0 { - return StatsResp{ + return &StatsResp{ TimeUnits: "days", - TopBlocked: []topAddrs{}, - TopClients: []topAddrs{}, - TopQueried: []topAddrs{}, + TopBlocked: []topAddrs{}, + TopClients: []topAddrs{}, + TopQueried: []topAddrs{}, + TopUpstreamsResponses: []topAddrs{}, + TopUpstreamsAvgTime: []topAddrsFloat{}, BlockedFiltering: []uint64{}, DNSQueries: []uint64{}, @@ -401,36 +421,33 @@ func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) { }, true } - timeUnit := Hours - if limit/24 > 7 { - timeUnit = Days - } - - units, firstID := s.loadUnits(limit) + units, curID := s.loadUnits(limit) if units == nil { - return StatsResp{}, false + return &StatsResp{}, false } - dnsQueries := statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NTotal }) - if timeUnit != Hours && len(dnsQueries) != int(limit/24) { - log.Fatalf("len(dnsQueries) != limit: %d %d", len(dnsQueries), limit) + return s.dataFromUnits(units, curID), true +} + +// dataFromUnits collects and returns the statistics data. +func (s *StatsCtx) dataFromUnits(units []*unitDB, curID uint32) (resp *StatsResp) { + topUpstreamsResponses, topUpstreamsAvgTime := topUpstreamsPairs(units) + + resp = &StatsResp{ + TopQueried: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.Domains }), + TopBlocked: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }), + TopUpstreamsResponses: topUpstreamsResponses, + TopUpstreamsAvgTime: topUpstreamsAvgTime, + TopClients: topsCollector(units, maxClients, nil, topClientPairs(s)), } - data := StatsResp{ - DNSQueries: dnsQueries, - BlockedFiltering: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }), - ReplacedSafebrowsing: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }), - ReplacedParental: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RParental] }), - TopQueried: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.Domains }), - TopBlocked: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }), - TopClients: topsCollector(units, maxClients, nil, topClientPairs(s)), - } + s.fillCollectedStats(resp, units, curID) // Total counters: sum := unitDB{ NResult: make([]uint64, resultLast), } - timeN := 0 + var timeN uint32 for _, u := range units { sum.NTotal += u.NTotal sum.TimeAvg += u.TimeAvg @@ -443,22 +460,83 @@ func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) { sum.NResult[RParental] += u.NResult[RParental] } - data.NumDNSQueries = sum.NTotal - data.NumBlockedFiltering = sum.NResult[RFiltered] - data.NumReplacedSafebrowsing = sum.NResult[RSafeBrowsing] - data.NumReplacedSafesearch = sum.NResult[RSafeSearch] - data.NumReplacedParental = sum.NResult[RParental] + resp.NumDNSQueries = sum.NTotal + resp.NumBlockedFiltering = sum.NResult[RFiltered] + resp.NumReplacedSafebrowsing = sum.NResult[RSafeBrowsing] + resp.NumReplacedSafesearch = sum.NResult[RSafeSearch] + resp.NumReplacedParental = sum.NResult[RParental] if timeN != 0 { - data.AvgProcessingTime = float64(sum.TimeAvg/uint32(timeN)) / 1000000 + resp.AvgProcessingTime = microsecondsToSeconds(float64(sum.TimeAvg / timeN)) } - data.TimeUnits = "hours" - if timeUnit == Days { - data.TimeUnits = "days" + return resp +} + +// fillCollectedStats fills data with collected statistics. +func (s *StatsCtx) fillCollectedStats(data *StatsResp, units []*unitDB, curID uint32) { + size := len(units) + data.TimeUnits = timeUnitsHours + + daysCount := size / 24 + if daysCount > 7 { + size = daysCount + data.TimeUnits = timeUnitsDays } - return data, true + data.DNSQueries = make([]uint64, size) + data.BlockedFiltering = make([]uint64, size) + data.ReplacedSafebrowsing = make([]uint64, size) + data.ReplacedParental = make([]uint64, size) + + if data.TimeUnits == timeUnitsDays { + s.fillCollectedStatsDaily(data, units, curID, size) + + return + } + + for i, u := range units { + data.DNSQueries[i] += u.NTotal + data.BlockedFiltering[i] += u.NResult[RFiltered] + data.ReplacedSafebrowsing[i] += u.NResult[RSafeBrowsing] + data.ReplacedParental[i] += u.NResult[RParental] + } +} + +// fillCollectedStatsDaily fills data with collected daily statistics. units +// must contain data for the count of days. +func (s *StatsCtx) fillCollectedStatsDaily( + data *StatsResp, + units []*unitDB, + curHour uint32, + days int, +) { + // Per time unit counters: 720 hours may span 31 days, so we skip data for + // the first hours in this case. align_ceil(24) + hours := countHours(curHour, days) + units = units[len(units)-hours:] + + for i := 0; i < len(units); i++ { + day := i / 24 + u := units[i] + + data.DNSQueries[day] += u.NTotal + data.BlockedFiltering[day] += u.NResult[RFiltered] + data.ReplacedSafebrowsing[day] += u.NResult[RSafeBrowsing] + data.ReplacedParental[day] += u.NResult[RParental] + } +} + +// countHours returns the number of hours in the last days. +func countHours(curHour uint32, days int) (n int) { + hoursInCurDay := int(curHour % 24) + if hoursInCurDay == 0 { + hoursInCurDay = 24 + } + + hoursInRestDays := (days - 1) * 24 + + return hoursInRestDays + hoursInCurDay } func topClientPairs(s *StatsCtx) (pg pairsGetter) { @@ -474,3 +552,73 @@ func topClientPairs(s *StatsCtx) (pg pairsGetter) { return clients } } + +// topUpstreamsPairs returns sorted lists of number of total responses and the +// average of processing time for each upstream. +func topUpstreamsPairs( + units []*unitDB, +) (topUpstreamsResponses []topAddrs, topUpstreamsAvgTime []topAddrsFloat) { + upstreamsResponses := topAddrs{} + upstreamsTimeSum := topAddrsFloat{} + + for _, u := range units { + for _, cp := range u.UpstreamsResponses { + upstreamsResponses[cp.Name] += cp.Count + } + + for _, cp := range u.UpstreamsTimeSum { + upstreamsTimeSum[cp.Name] += float64(cp.Count) + } + } + + upstreamsAvgTime := topAddrsFloat{} + + for u, n := range upstreamsResponses { + total := upstreamsTimeSum[u] + + if total != 0 { + upstreamsAvgTime[u] = microsecondsToSeconds(total / float64(n)) + } + } + + upstreamsPairs := convertMapToSlice(upstreamsResponses, maxUpstreams) + topUpstreamsResponses = convertTopSlice(upstreamsPairs) + + return topUpstreamsResponses, prepareTopUpstreamsAvgTime(upstreamsAvgTime) +} + +// microsecondsToSeconds converts microseconds to seconds. +// +// NOTE: Frontend expects time duration in seconds as floating-point number +// with double precision. +func microsecondsToSeconds(n float64) (r float64) { + const micro = 1e-6 + + return n * micro +} + +// prepareTopUpstreamsAvgTime returns sorted list of average processing times +// of the DNS requests from each upstream. +func prepareTopUpstreamsAvgTime( + upstreamsAvgTime topAddrsFloat, +) (topUpstreamsAvgTime []topAddrsFloat) { + keys := maps.Keys(upstreamsAvgTime) + + slices.SortFunc(keys, func(a, b string) (res int) { + switch x, y := upstreamsAvgTime[a], upstreamsAvgTime[b]; { + case x > y: + return -1 + case x < y: + return +1 + default: + return 0 + } + }) + + topUpstreamsAvgTime = make([]topAddrsFloat, 0, len(upstreamsAvgTime)) + for _, k := range keys { + topUpstreamsAvgTime = append(topUpstreamsAvgTime, topAddrsFloat{k: upstreamsAvgTime[k]}) + } + + return topUpstreamsAvgTime +} diff --git a/internal/stats/unit_internal_test.go b/internal/stats/unit_internal_test.go new file mode 100644 index 00000000..ea3bff50 --- /dev/null +++ b/internal/stats/unit_internal_test.go @@ -0,0 +1,177 @@ +package stats + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnit_Deserialize(t *testing.T) { + testCases := []struct { + db *unitDB + name string + want unit + }{{ + name: "empty", + want: unit{ + domains: map[string]uint64{}, + blockedDomains: map[string]uint64{}, + clients: map[string]uint64{}, + nResult: []uint64{0, 0, 0, 0, 0, 0}, + id: 0, + nTotal: 0, + timeSum: 0, + upstreamsResponses: map[string]uint64{}, + upstreamsTimeSum: map[string]uint64{}, + }, + db: &unitDB{ + NResult: []uint64{0, 0, 0, 0, 0, 0}, + Domains: []countPair{}, + BlockedDomains: []countPair{}, + Clients: []countPair{}, + NTotal: 0, + TimeAvg: 0, + UpstreamsResponses: []countPair{}, + UpstreamsTimeSum: []countPair{}, + }, + }, { + name: "basic", + want: unit{ + domains: map[string]uint64{ + "example.com": 1, + }, + blockedDomains: map[string]uint64{ + "example.net": 1, + }, + clients: map[string]uint64{ + "127.0.0.1": 2, + }, + nResult: []uint64{0, 1, 1, 0, 0, 0}, + id: 0, + nTotal: 2, + timeSum: 246912, + upstreamsResponses: map[string]uint64{ + "1.2.3.4": 2, + }, + upstreamsTimeSum: map[string]uint64{ + "1.2.3.4": 246912, + }, + }, + db: &unitDB{ + NResult: []uint64{0, 1, 1, 0, 0, 0}, + Domains: []countPair{{ + "example.com", 1, + }}, + BlockedDomains: []countPair{{ + "example.net", 1, + }}, + Clients: []countPair{{ + "127.0.0.1", 2, + }}, + NTotal: 2, + TimeAvg: 123456, + UpstreamsResponses: []countPair{{ + "1.2.3.4", 2, + }}, + UpstreamsTimeSum: []countPair{{ + "1.2.3.4", 246912, + }}, + }, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := unit{} + got.deserialize(tc.db) + require.Equal(t, tc.want, got) + }) + } +} + +func TestTopUpstreamsPairs(t *testing.T) { + testCases := []struct { + db *unitDB + name string + wantResponses []topAddrs + wantAvgTime []topAddrsFloat + }{{ + name: "empty", + db: &unitDB{ + NResult: []uint64{0, 0, 0, 0, 0, 0}, + Domains: []countPair{}, + BlockedDomains: []countPair{}, + Clients: []countPair{}, + NTotal: 0, + TimeAvg: 0, + UpstreamsResponses: []countPair{}, + UpstreamsTimeSum: []countPair{}, + }, + wantResponses: []topAddrs{}, + wantAvgTime: []topAddrsFloat{}, + }, { + name: "basic", + db: &unitDB{ + NResult: []uint64{0, 0, 0, 0, 0, 0}, + Domains: []countPair{}, + BlockedDomains: []countPair{}, + Clients: []countPair{}, + NTotal: 0, + TimeAvg: 0, + UpstreamsResponses: []countPair{{ + "1.2.3.4", 2, + }}, + UpstreamsTimeSum: []countPair{{ + "1.2.3.4", 246912, + }}, + }, + wantResponses: []topAddrs{{ + "1.2.3.4": 2, + }}, + wantAvgTime: []topAddrsFloat{{ + "1.2.3.4": 0.123456, + }}, + }, { + name: "sorted", + db: &unitDB{ + NResult: []uint64{0, 0, 0, 0, 0, 0}, + Domains: []countPair{}, + BlockedDomains: []countPair{}, + Clients: []countPair{}, + NTotal: 0, + TimeAvg: 0, + UpstreamsResponses: []countPair{ + {"3.3.3.3", 8}, + {"2.2.2.2", 4}, + {"4.4.4.4", 16}, + {"1.1.1.1", 2}, + }, + UpstreamsTimeSum: []countPair{ + {"3.3.3.3", 800_000_000}, + {"2.2.2.2", 40_000_000}, + {"4.4.4.4", 16_000_000_000}, + {"1.1.1.1", 2_000_000}, + }, + }, + wantResponses: []topAddrs{ + {"4.4.4.4": 16}, + {"3.3.3.3": 8}, + {"2.2.2.2": 4}, + {"1.1.1.1": 2}, + }, + wantAvgTime: []topAddrsFloat{ + {"4.4.4.4": 1000}, + {"3.3.3.3": 100}, + {"2.2.2.2": 10}, + {"1.1.1.1": 1}, + }, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + gotResponses, gotAvgTime := topUpstreamsPairs([]*unitDB{tc.db}) + assert.Equal(t, tc.wantResponses, gotResponses) + assert.Equal(t, tc.wantAvgTime, gotAvgTime) + }) + } +} diff --git a/internal/tools/go.mod b/internal/tools/go.mod index 8cdd6e81..5c1b3dec 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -8,28 +8,29 @@ require ( github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 github.com/kisielk/errcheck v1.6.3 github.com/kyoh86/looppointer v0.2.1 - github.com/securego/gosec/v2 v2.16.0 - github.com/uudashr/gocognit v1.0.7 - golang.org/x/tools v0.11.1 - golang.org/x/vuln v1.0.0 - // TODO(a.garipov): Return to tagged releases once a new one appears. - honnef.co/go/tools v0.5.0-0.dev.0.20230709092525-bc759185c5ee + github.com/securego/gosec/v2 v2.17.0 + // TODO(a.garipov): Return to latest once the release is tagged + // correctly. See uudashr/gocognit#31. + github.com/uudashr/gocognit v1.0.8-0.20230906062305-bc9ca12659bf + golang.org/x/tools v0.13.0 + golang.org/x/vuln v1.0.1 + honnef.co/go/tools v0.4.5 mvdan.cc/gofumpt v0.5.0 - mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868 + mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0 ) require ( github.com/BurntSushi/toml v1.3.2 // indirect + github.com/ccojocar/zxcvbn-go v1.0.1 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/gookit/color v1.5.4 // indirect github.com/kyoh86/nolint v0.0.1 // indirect - github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b // indirect + golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.10.0 // indirect + golang.org/x/sys v0.12.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/tools/go.sum b/internal/tools/go.sum index 609f4a84..e3f2edd1 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -1,6 +1,7 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ccojocar/zxcvbn-go v1.0.1 h1:+sxrANSCj6CdadkcMnvde/GWU1vZiiXRbqYSCalV4/4= +github.com/ccojocar/zxcvbn-go v1.0.1/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= @@ -14,8 +15,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8= @@ -28,19 +29,15 @@ github.com/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fx github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw= github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU= github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/securego/gosec/v2 v2.16.0 h1:Pi0JKoasQQ3NnoRao/ww/N/XdynIB9NRYYZT5CyOs5U= -github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI= -github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/securego/gosec/v2 v2.17.0 h1:ZpAStTDKY39insEG9OH6kV3IkhQZPTq9a9eGOLOjcdI= +github.com/securego/gosec/v2 v2.17.0/go.mod h1:lt+mgC91VSmriVoJLentrMkRCYs+HLTBnUFUBuhV2hc= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/uudashr/gocognit v1.0.7 h1:e9aFXgKgUJrQ5+bs61zBigmj7bFJ/5cC6HmMahVzuDo= -github.com/uudashr/gocognit v1.0.7/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= +github.com/uudashr/gocognit v1.0.8-0.20230906062305-bc9ca12659bf h1:LA5CHw6L5BvI0RjqvBYa9+3hXAL2rhuAJPtS5rS2a2k= +github.com/uudashr/gocognit v1.0.8-0.20230906062305-bc9ca12659bf/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -52,8 +49,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b h1:3dfup1Bt5y1sKG6rbyAX4qNymwAtJcqx+Aqm1DPP/Qg= -golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 h1:j3D9DvWRpUfIyFfDPws7LoIZ2MAI1OJHdQXtTnYtN+k= +golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= @@ -66,7 +63,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -82,24 +79,24 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= -golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= -golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= -golang.org/x/vuln v1.0.0 h1:tYLAU3jD9LQr98Y+3el06lWyGMCnvzw06PIWP3LIy7g= -golang.org/x/vuln v1.0.0/go.mod h1:V0eyhHwaAaHrt42J9bgrN6rd12f6GU4T0Lu0ex2wDg4= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU= +golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -107,9 +104,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.5.0-0.dev.0.20230709092525-bc759185c5ee h1:mpyvMqtlVZTwEv78QL3S2ZDTMHMO1fgNwr2kC7+K7oU= -honnef.co/go/tools v0.5.0-0.dev.0.20230709092525-bc759185c5ee/go.mod h1:GUV+uIBCLpdf0/v6UhHHG/yzI/z6qPskBeQCjcNB96k= +honnef.co/go/tools v0.4.5 h1:YGD4H+SuIOOqsyoLOpZDWcieM28W47/zRO7f+9V3nvo= +honnef.co/go/tools v0.4.5/go.mod h1:GUV+uIBCLpdf0/v6UhHHG/yzI/z6qPskBeQCjcNB96k= mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= -mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868 h1:F4Q7pXcrU9UiU1fq0ZWqSOxKjNAteRuDr7JDk7uVLRQ= -mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868/go.mod h1:6ZaiQyI7Tiq0HQ56g6N8TlkSd80/LyagZeaw8mb7jYE= +mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0 h1:NAENkqZ+Xofhqs4R4Af+i3HpZj1M23SFn/lHfRh1D4E= +mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0/go.mod h1:flQN1deud3vIpPdF88533Lpp/MvzGLgPIPjB1kgBf4I= diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index 7c68a47b..3ea61c33 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -4,6 +4,107 @@ ## v0.108.0: API changes +## v0.107.37: API changes + +### The new field `"fallback_dns"` in `UpstreamsConfig` object + +* The new field `"fallback_dns"` in `POST /control/test_upstream_dns` is the + list of fallback DNS servers to test. + +### The new field `"fallback_dns"` in `DNSConfig` object + +* The new field `"fallback_dns"` in `GET /control/dns_info` and `POST + /control/dns_config` is the list of fallback DNS servers used when upstream + DNS servers are not responding. + +### Deprecated blocked services APIs + +* The `GET /control/blocked_services/list` HTTP API; use the new `GET + /control/blocked_services/get` API instead. + +* The `POST /control/blocked_services/set` HTTP API; use the new `PUT + /control/blocked_services/update` API instead. + +### New blocked services APIs + +* The new `GET /control/blocked_services/get` HTTP API. + +* The new `PUT /control/blocked_services/update` HTTP API allows config + updates. + +These APIs accept and return a JSON object with the following format: + +```json +{ + "schedule": { + "time_zone": "Local", + "sun": { + "start": 46800000, + "end": 82800000 + } + }, + "ids": [ + "vk" + ] +} +``` + +### `/control/clients` HTTP APIs + +The following HTTP APIs have been changed: + +* `GET /control/clients`; +* `GET /control/clients/find?ip0=...&ip1=...&ip2=...`; +* `POST /control/clients/add`; +* `POST /control/clients/update`; + +The new field `blocked_services_schedule` has been added to JSON objects. It +has the following format: + +```json +{ + "time_zone": "Local", + "sun": { + "start": 0, + "end": 86400000 + }, + "mon": { + "start": 60000, + "end": 82800000 + }, + "thu": { + "start": 120000, + "end": 79200000 + }, + "tue": { + "start": 180000, + "end": 75600000 + }, + "wed": { + "start": 240000, + "end": 72000000 + }, + "fri": { + "start": 300000, + "end": 68400000 + }, + "sat": { + "start": 360000, + "end": 64800000 + } +} +``` + +## v0.107.36: API changes + +### The new fields `"top_upstreams_responses"` and `"top_upstreams_avg_time"` in `Stats` object + +* The new field `"top_upstreams_responses"` in `GET /control/stats` method + shows the total number of responses from each upstream. + +* The new field `"top_upstrems_avg_time"` in `GET /control/stats` method shows + the average processing time in seconds of requests from each upstream. + ## v0.107.30: API changes ### `POST /control/version.json` and `GET /control/dhcp/interfaces` content type @@ -19,8 +120,14 @@ ```json { - "target": {"domain":"example.com","answer":"answer-to-update"}, - "update": {"domain":"example.com","answer":"new-answer"} + "target": { + "domain": "example.com", + "answer": "answer-to-update" + }, + "update": { + "domain": "example.com", + "answer": "new-answer" + } } ``` @@ -78,7 +185,9 @@ return a JSON object with the following format: { "enabled": true, "interval": 3600, - "ignored": ["example.com"], + "ignored": [ + "example.com" + ] } ``` @@ -109,7 +218,9 @@ accept and return a JSON object with the following format: "enabled": true, "anonymize_client_ip": false, "interval": 3600, - "ignored": ["example.com"], + "ignored": [ + "example.com" + ] } ``` @@ -241,7 +352,7 @@ JSON object with the following format: ```json { - "name":"user name", + "name": "user name", "language": "en", "theme": "auto" } @@ -317,8 +428,7 @@ the filters must be presented in a JSON object with the following format: ```json { - "rules": - [ + "rules": [ "||example.com^", "# comment", "@@||www.example.com^" diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index b1519843..52331c7f 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1001,6 +1001,9 @@ '$ref': '#/components/schemas/BlockedServicesAll' '/blocked_services/list': 'get': + 'deprecated': true + 'description': > + Deprecated: Use `GET /blocked_services/get` instead. 'tags': - 'blocked_services' 'operationId': 'blockedServicesList' @@ -1014,6 +1017,9 @@ '$ref': '#/components/schemas/BlockedServicesArray' '/blocked_services/set': 'post': + 'deprecated': true + 'description': > + Deprecated: Use `PUT /blocked_services/update` instead. 'tags': - 'blocked_services' 'operationId': 'blockedServicesSet' @@ -1026,6 +1032,34 @@ 'responses': '200': 'description': 'OK.' + '/blocked_services/get': + 'get': + 'tags': + - 'blocked_services' + 'operationId': 'blockedServicesSchedule' + 'summary': 'Get blocked services' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/BlockedServicesSchedule' + '/blocked_services/update': + 'put': + 'tags': + - 'blocked_services' + 'operationId': 'blockedServicesScheduleUpdate' + 'summary': 'Update blocked services' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/BlockedServicesSchedule' + 'required': true + 'responses': + '200': + 'description': 'OK.' '/rewrite/list': 'get': 'tags': @@ -1398,6 +1432,16 @@ 'example': - 'tls://1.1.1.1' - 'tls://1.0.0.1' + 'fallback_dns': + 'type': 'array' + 'description': > + List of fallback DNS servers used when upstream DNS servers are not + responding. Empty value will clear the list. + 'items': + 'type': 'string' + 'example': + - '8.8.8.8' + - '1.1.1.1:53' 'upstream_dns_file': 'type': 'string' 'protection_enabled': @@ -1459,7 +1503,7 @@ - 'tls://1.0.0.1' 'UpstreamsConfig': 'type': 'object' - 'description': 'Upstreams configuration' + 'description': 'Upstream configuration to be tested' 'required': - 'bootstrap_dns' - 'upstream_dns' @@ -1467,8 +1511,7 @@ 'bootstrap_dns': 'type': 'array' 'description': > - Bootstrap servers, port is optional after colon. Empty value will - reset it to default values. + Bootstrap DNS servers, port is optional after colon. 'items': 'type': 'string' 'example': @@ -1477,18 +1520,25 @@ 'upstream_dns': 'type': 'array' 'description': > - Upstream servers, port is optional after colon. Empty value will - reset it to default values. + Upstream DNS servers, port is optional after colon. 'items': 'type': 'string' 'example': - 'tls://1.1.1.1' - 'tls://1.0.0.1' + 'fallback_dns': + 'type': 'array' + 'description': > + Fallback DNS servers, port is optional after colon. + 'items': + 'type': 'string' + 'example': + - '8.8.8.8' + - '1.1.1.1:53' 'private_upstream': 'type': 'array' 'description': > - Local PTR resolvers, port is optional after colon. Empty value will - reset it to default values. + Local PTR resolvers, port is optional after colon. 'items': 'type': 'string' 'example': @@ -1728,7 +1778,7 @@ 'avg_processing_time': 'type': 'number' 'format': 'float' - 'description': 'Average time in milliseconds on processing a DNS' + 'description': 'Average time in seconds on processing a DNS request' 'example': 0.34 'top_queried_domains': 'type': 'array' @@ -1742,6 +1792,19 @@ 'type': 'array' 'items': '$ref': '#/components/schemas/TopArrayEntry' + 'top_upstreams_responses': + 'type': 'array' + 'description': 'Total number of responses from each upstream.' + 'items': + '$ref': '#/components/schemas/TopArrayEntry' + 'maxItems': 100 + 'top_upstreams_avg_time': + 'type': 'array' + 'description': > + Average processing time in seconds of requests from each upstream. + 'items': + '$ref': '#/components/schemas/TopArrayEntry' + 'maxItems': 100 'dns_queries': 'type': 'array' 'items': @@ -1761,12 +1824,13 @@ 'TopArrayEntry': 'type': 'object' 'description': > - Represent the number of hits per key (domain or client IP). + Represent the number of hits or time duration per key (url, domain, or + client IP). 'properties': 'domain_or_ip': - 'type': 'integer' + 'type': 'number' 'additionalProperties': - 'type': 'integer' + 'type': 'number' 'StatsConfig': 'type': 'object' 'description': 'Statistics configuration' @@ -2471,6 +2535,54 @@ 'type': 'boolean' 'youtube': 'type': 'boolean' + 'Schedule': + 'type': 'object' + 'description': > + Sets periods of inactivity for filtering blocked services. The + schedule contains 7 days (Sunday to Saturday) and a time zone. + 'properties': + 'time_zone': + 'description': > + Time zone name according to IANA time zone database. For example + `Europe/Brussels`. `Local` represents the system's local time + zone. + 'type': 'string' + 'sun': + '$ref': '#/components/schemas/DayRange' + 'mon': + '$ref': '#/components/schemas/DayRange' + 'tue': + '$ref': '#/components/schemas/DayRange' + 'wed': + '$ref': '#/components/schemas/DayRange' + 'thu': + '$ref': '#/components/schemas/DayRange' + 'fri': + '$ref': '#/components/schemas/DayRange' + 'sat': + '$ref': '#/components/schemas/DayRange' + 'DayRange': + 'type': 'object' + 'description': > + The single interval within a day. It begins at the `start` and ends + before the `end`. + 'properties': + 'start': + 'type': 'number' + 'description': > + The number of milliseconds elapsed from the start of a day. It + must be less than `end` and is expected to be rounded to minutes. + So the maximum value is `86340000` (23 hours and 59 minutes). + 'minimum': 0 + 'maximum': 86340000 + 'end': + 'type': 'number' + 'description': > + The number of milliseconds elapsed from the start of a day. It is + expected to be rounded to minutes. The maximum value is `86400000` + (24 hours). + 'minimum': 0 + 'maximum': 86400000 'Client': 'type': 'object' 'description': 'Client information.' @@ -2499,6 +2611,8 @@ '$ref': '#/components/schemas/SafeSearchConfig' 'use_global_blocked_services': 'type': 'boolean' + 'blocked_services_schedule': + '$ref': '#/components/schemas/Schedule' 'blocked_services': 'type': 'array' 'items': @@ -2779,6 +2893,17 @@ - 'name' - 'rules' 'type': 'object' + 'BlockedServicesSchedule': + 'type': 'object' + 'properties': + 'schedule': + '$ref': '#/components/schemas/Schedule' + 'ids': + 'description': > + The names of the blocked services. + 'type': 'array' + 'items': + 'type': 'string' 'CheckConfigRequest': 'type': 'object' 'description': 'Configuration to be checked' diff --git a/openapi/v1.yaml b/openapi/v1.yaml new file mode 100644 index 00000000..adab6d4d --- /dev/null +++ b/openapi/v1.yaml @@ -0,0 +1,5041 @@ +'openapi': '3.0.3' +'info': + 'contact': + 'email': 'devteam@adguard.com' + 'name': 'AdGuard Home' + 'url': 'https://github.com/AdguardTeam/AdGuardHome' + 'description': | + **!! WARNING! API IS AT THE DRAFT STAGE! THINGS WILL BREAK! !!** + + AdGuard Home REST API, V1 **DRAFT**. Our administration web interface is + built on top of this REST API. + + This API is currently a **DRAFT** and is not covered by any stability + guarantees. Once this API reaches maturity, the old `/control/` API will + mostly be removed. + + ## Information For API Users + + * Empty arrays are always sent by the backend, unless documented + otherwise. If the backend doesn't, it's a backend error. + + * `PATCH` requests with JSON bodies use RFC 7396 JSON Merge Patch unless + documented otherwise. + + * The property `x-error-class` on plain text error responses suggests, + which class of error should be used if the API user wants to wrap it + into an object. The property `x-error-code` suggest the error code for + the error object. The code usually goes into the `code` property, and + the content, into `msg`. + + * The property `x-skip-web-api` on operations suggests API clients for + web, like our frontend, to skip this operation in their generated code. + + * The header `Server` will be set to `AdGuardHome/<>`. For + example: `AdGuardHome/v0.107.0-a.42+abcd1234`. + + ## Conventions For API Authors + + ### Naming + + * `CapitalCamelCase` for entities. + + * Initialisms are spelled like `DhcpSettings` and not `DHCPSettings`. + + * `lower_snake_case` for path and query parameters. + + * No unit suffices. + + * Path parameters's names start with `Path`; query, with `Query`. + + * Requests end with `Req`; responses, with `Resp`. + + ### Structure + + * Add `400` and `422` responses to requests that accept data. + + * Add `401` responses, unless the method can work without authorization. + + * Add `500` responses. + + * Descriptions are always on their own lines. + + * Don't add a description if there is already one a level above. + + * Five levels of indentation max, except for descriptions and array + items. + + * Keep things in alphabetical order. + + * Mark required things as such. Document possibly-absent fields **both** + in `required` **and** in `description`, if there is one. + + * Prefer flat objects. Example: `resp.top_user`, not `resp.top.user.val`. + + * Prefer to make it easier for the frontend where possible. + + * Provide examples for requests and responses. If examples are provided + elsewhere, document that. + + * Summaries and descriptions with dots. + + * Top-level value in a JSON request or response must be an object. + + ### Types + + * Add `'maximum': 65535` for 16-bit unsigned integers (for example, port + numbers). + + * Add `'minimum': 0` for unsigned integers. + + * Duration in milliseconds. Time in milliseconds in the Unix epoch. Both + of type `double`, because that is easier for the JS frontend. + + * Integers are always `int64`, numbers are always `double. + + 'license': + 'name': 'GNU General Public License v3.0' + 'url': 'https://www.gnu.org/licenses/gpl-3.0.txt' + 'title': 'AdGuard Home V1 DRAFT API' + 'version': '0.108' + +'servers': +- 'description': > + The V1 HTTP API namespace. + 'url': '/api/v1' + +'security': +- 'basicAuth': [] + +'tags': +- 'description': > + Authorization and account management. + 'name': 'accounts' +- 'description': > + Configuration and settings for Apple products. + 'name': 'apple' +- 'description': > + Runtime and persistent client information. + 'name': 'clients' +- 'description': > + DHCP server methods. + 'name': 'dhcp' +- 'description': > + First-time install configuration handlers. Will not be available once the + installation is done. + 'name': 'install' +- 'description': > + Query logs. + 'name': 'log' +- 'description': > + Filter lists, blocked services, and custom filtering rules. + 'name': 'protection' +- 'description': > + Settings management. + 'name': 'settings' +- 'description': > + Query, filtering, system, and other statistics. + 'name': 'stats' +- 'description': > + Information about the AdGuard Home server and the host system. + 'name': 'system' + +'paths': + '/health-check': + 'get': + 'operationId': 'HealthCheck' + 'responses': + '200': + 'description': > + An OK response. + 'content': + 'text/plain': + 'example': 'OK' + 'servers': + - 'url': '/' + 'summary': 'Check if the server is up.' + 'tags': + - 'system' + + '/accounts/profile': + 'get': + 'operationId': 'GetV1AccountsProfile' + 'responses': + '200': + '$ref': '#/components/responses/GetV1AccountsProfileResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get the profile of the current user.' + 'tags': + - 'accounts' + 'patch': + 'operationId': 'PatchV1AccountsProfile' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1AccountsProfileReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1AccountsProfileResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update the profile of the current user.' + 'tags': + - 'accounts' + + '/accounts/session': + 'delete': + 'operationId': 'DeleteV1AccountsSession' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Log out of the system.' + 'tags': + - 'accounts' + 'post': + 'operationId': 'PostV1AccountsSession' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1AccountsSessionReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Log into the system.' + 'tags': + - 'accounts' + + '/apple/doh.mobileconfig': + 'get': + 'operationId': 'GetV1AppleDohMobileconfig' + 'parameters': + - '$ref': '#/components/parameters/QueryClientId' + - '$ref': '#/components/parameters/QueryHost' + 'responses': + '200': + '$ref': '#/components/responses/GetV1AppleDohMobileconfigResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get a DNS-over-HTTPS .mobileconfig.' + 'tags': + - 'apple' + 'x-skip-web-api': true + + '/apple/dot.mobileconfig': + 'get': + 'operationId': 'GetV1AppleDotMobileconfig' + 'parameters': + - '$ref': '#/components/parameters/QueryHost' + - '$ref': '#/components/parameters/QueryClientId' + 'responses': + '200': + '$ref': '#/components/responses/GetV1AppleDotMobileconfigResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get a DNS-over-HTTPS .mobileconfig.' + 'tags': + - 'apple' + 'x-skip-web-api': true + + '/clients/persistent': + 'get': + 'operationId': 'GetV1ClientsPersistent' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ClientsPersistentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all persistent clients.' + 'tags': + - 'clients' + 'post': + 'operationId': 'PostV1ClientsPersistent' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ClientsPersistentReq' + 'responses': + '201': + '$ref': '#/components/responses/PostV1ClientsPersistentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Create a new persistent client.' + 'tags': + - 'clients' + + '/clients/persistent/{client_uid}': + 'delete': + 'operationId': 'DeleteV1ClientPersistent' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Delete a persistent client.' + 'tags': + - 'clients' + 'parameters': + - '$ref': '#/components/parameters/PathClientUid' + 'patch': + 'operationId': 'PatchV1ClientPersistent' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1ClientPersistentReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1ClientPersistentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update a persistent client.' + 'tags': + - 'clients' + + '/clients/runtime': + 'get': + 'operationId': 'GetV1ClientsRuntime' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ClientsRuntimeResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all runtime clients.' + 'tags': + - 'clients' + + '/dhcp/leases': + 'get': + 'operationId': 'GetV1DhcpLeases' + 'responses': + '200': + '$ref': '#/components/responses/GetV1DhcpLeasesResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all dynamic and static DHCP leases.' + 'tags': + - 'dhcp' + 'post': + 'operationId': 'PostV1DhcpLeases' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1DhcpLeasesReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1DhcpLeasesResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Create a new static DHCP lease.' + 'tags': + - 'dhcp' + + '/dhcp/leases/{lease_uid}': + 'delete': + 'operationId': 'DeleteV1DhcpLease' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Delete a static DHCP lease.' + 'tags': + - 'dhcp' + 'parameters': + - '$ref': '#/components/parameters/PathLeaseUid' + 'patch': + 'operationId': 'PatchV1DhcpLease' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1DhcpLeaseReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1DhcpLeaseResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update a static DHCP lease.' + 'tags': + - 'dhcp' + + '/dhcp/status': + 'get': + 'operationId': 'GetV1DhcpStatus' + 'responses': + '200': + '$ref': '#/components/responses/GetV1DhcpStatusResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get DHCP server status.' + 'tags': + - 'dhcp' + + '/install/check': + 'post': + 'operationId': 'PostV1InstallCheck' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1InstallCheckReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1InstallCheckResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Check initial configuration.' + 'tags': + - 'install' + + '/install/configure': + 'post': + 'operationId': 'PostV1InstallConfigure' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1InstallConfigureReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Apply initial configuration.' + 'tags': + - 'install' + + '/install/info': + 'get': + 'operationId': 'GetV1InstallInfo' + 'responses': + '200': + '$ref': '#/components/responses/GetV1InstallInfoResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get initial configuration information.' + 'tags': + - 'install' + + '/log/clear': + 'post': + 'operationId': 'PostV1LogClear' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1LogClearReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Clear the whole query log.' + 'tags': + - 'log' + + '/log/search': + 'get': + 'operationId': 'GetV1LogSearch' + 'parameters': + - '$ref': '#/components/parameters/QueryBefore' + - '$ref': '#/components/parameters/QueryLimit' + - '$ref': '#/components/parameters/QueryReason' + - '$ref': '#/components/parameters/QueryTerm' + 'responses': + '200': + '$ref': '#/components/responses/GetV1LogSearchResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Search query logs.' + 'tags': + - 'log' + + '/protection/blocked_services': + 'get': + 'operationId': 'GetV1ProtectionBlockedServices' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionBlockedServicesResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get blocked services.' + 'tags': + - 'protection' + 'put': + 'operationId': 'PutV1ProtectionBlockedServices' + 'requestBody': + '$ref': '#/components/requestBodies/PutV1ProtectionBlockedServicesReq' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionBlockedServicesResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Replace blocked services.' + 'tags': + - 'protection' + + '/protection/check_custom_rules': + 'post': + 'operationId': 'PostV1ProtectionCheckCustomRules' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionCheckCustomRulesReq' + 'responses': + '201': + '$ref': '#/components/responses/PostV1ProtectionCheckCustomRulesResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Check custom filtering rules.' + 'tags': + - 'protection' + + '/protection/custom_rules': + 'get': + 'operationId': 'GetV1ProtectionCustomRules' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionCustomRulesResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get custom rules.' + 'tags': + - 'protection' + 'put': + 'operationId': 'PutV1ProtectionCustomRules' + 'requestBody': + '$ref': '#/components/requestBodies/PutV1ProtectionCustomRulesReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Replace custom rules.' + 'tags': + - 'protection' + + '/protection/dns_rewrites': + 'get': + 'operationId': 'GetV1ProtectionDnsRewrites' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionDnsRewritesResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all classic DNS rewrites.' + 'tags': + - 'protection' + 'post': + 'operationId': 'PostV1ProtectionDnsRewrites' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionDnsRewritesReq' + 'responses': + '201': + '$ref': '#/components/responses/PostV1ProtectionDnsRewritesResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Add a new classic DNS rewrite.' + 'tags': + - 'protection' + + '/protection/dns_rewrites/{dns_rewrite_uid}': + 'delete': + 'operationId': 'DeleteV1ProtectionDnsRewrite' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Delete a classic DNS rewrite.' + 'tags': + - 'protection' + 'parameters': + - '$ref': '#/components/parameters/PathDnsRewriteUid' + + '/protection/filters': + 'get': + 'operationId': 'GetV1ProtectionFilters' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionFiltersResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all filters.' + 'tags': + - 'protection' + 'post': + 'operationId': 'PostV1ProtectionFilters' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionFiltersReq' + 'responses': + '201': + '$ref': '#/components/responses/PostV1ProtectionFiltersResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Add a new filter.' + 'tags': + - 'protection' + + '/protection/filters/{filter_uid}': + 'delete': + 'operationId': 'DeleteV1ProtectionFilter' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Delete a filter.' + 'tags': + - 'protection' + 'parameters': + - '$ref': '#/components/parameters/PathFilterUid' + 'patch': + 'operationId': 'PatchV1ProtectionFilter' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1ProtectionFilterReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1ProtectionFilterResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': > + Update a filter's settings. + 'tags': + - 'protection' + + '/protection/refresh_filters': + 'post': + 'operationId': 'PostV1ProtectionRefreshFilters' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionRefreshFiltersReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1ProtectionRefreshFiltersResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': > + Refresh all filters. + 'tags': + - 'protection' + + '/protection/refresh_filters/{filter_uid}': + 'parameters': + - '$ref': '#/components/parameters/PathFilterUid' + 'post': + 'operationId': 'PostV1ProtectionRefreshFilter' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionRefreshFilterReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1ProtectionRefreshFilterResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': > + Refresh a filter. + 'tags': + - 'protection' + + '/settings/all': + 'get': + 'operationId': 'GetV1SettingsAll' + 'responses': + '200': + '$ref': '#/components/responses/GetV1SettingsAllResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all settings.' + 'tags': + - 'settings' + + '/settings/dhcp': + 'patch': + 'operationId': 'PatchV1SettingsDhcp' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsDhcpReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsDhcpResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update DHCP server settings.' + 'tags': + - 'settings' + + '/settings/dns': + 'patch': + 'operationId': 'PatchV1SettingsDns' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsDnsReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsDnsResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update DNS server settings.' + 'tags': + - 'settings' + + '/settings/dns/access': + 'get': + 'description': > + Get DNS access settings. This is a separate API, because these lists + can become quite big. + 'operationId': 'GetV1SettingsDnsAccess' + 'responses': + '200': + '$ref': '#/components/responses/GetV1SettingsDnsAccessResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get DNS access settings.' + 'tags': + - 'settings' + 'put': + 'description': > + Update DNS access settings. This is a separate API, because these lists + can become quite big. + 'operationId': 'PutV1SettingsDnsAccess' + 'requestBody': + '$ref': '#/components/requestBodies/PutV1SettingsDnsAccessReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update DNS access settings.' + 'tags': + - 'settings' + + '/settings/dns/check': + 'post': + 'operationId': 'PostV1SettingsDnsCheck' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1SettingsDnsCheckReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1SettingsDnsCheckResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Check DNS upstream settings.' + 'tags': + - 'settings' + + '/settings/http': + 'patch': + 'operationId': 'PatchV1SettingsHttp' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsHttpReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsHttpResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update web interface settings.' + 'tags': + - 'settings' + + '/settings/log': + 'patch': + 'operationId': 'PatchV1SettingsLog' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsLogReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsLogResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update query logging settings.' + 'tags': + - 'settings' + + '/settings/protection': + 'patch': + 'operationId': 'PatchV1SettingsProtection' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsProtectionReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsProtectionResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update protection settings.' + 'tags': + - 'settings' + + '/settings/stats': + 'patch': + 'operationId': 'PatchV1SettingsStats' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsStatsReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsStatsResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update statistics settings.' + 'tags': + - 'settings' + + '/settings/tls': + 'patch': + 'operationId': 'PatchV1SettingsTls' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsTlsReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsTlsResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update TLS and encryption settings.' + 'tags': + - 'settings' + + '/settings/tls/check': + 'post': + 'operationId': 'PostV1SettingsTlsCheck' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1SettingsTlsCheckReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1SettingsTlsCheckResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Check TLS and encryption settings.' + 'tags': + - 'settings' + + '/stats/all': + 'get': + 'operationId': 'GetV1StatsAll' + 'responses': + '200': + '$ref': '#/components/responses/GetV1StatsAllResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all statistics.' + 'tags': + - 'stats' + + '/stats/clear': + 'post': + 'operationId': 'PostV1StatsClear' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1StatsClearReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Clear all statistics.' + 'tags': + - 'stats' + + '/system/info': + 'get': + 'operationId': 'GetV1SystemInfo' + 'responses': + '200': + '$ref': '#/components/responses/GetV1SystemInfoResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get server information.' + 'tags': + - 'system' + + '/system/reset': + 'post': + 'operationId': 'PostV1SystemReset' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1SystemResetReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1SystemResetResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Reset all settings to defaults.' + 'tags': + - 'system' + + '/system/update': + 'post': + 'operationId': 'PostV1SystemUpdate' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1SystemUpdateReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1SystemUpdateResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update AdGuard Home.' + 'tags': + - 'system' + +'components': + 'parameters': + 'PathDnsRewriteUid': + 'description': > + DNS rewrite ID. + 'example': 'abcd1234' + 'in': 'path' + 'name': 'dns_rewrite_uid' + 'required': true + 'schema': + '$ref': '#/components/schemas/Uid' + + 'PathClientUid': + 'description': > + The unique ID of a client. + 'example': 'abcd1234' + 'in': 'path' + 'name': 'client_uid' + 'required': true + 'schema': + '$ref': '#/components/schemas/Uid' + + 'PathFilterUid': + 'description': > + The ID of a filter. + 'example': 'abcd1234' + 'in': 'path' + 'name': 'filter_uid' + 'required': true + 'schema': + '$ref': '#/components/schemas/Uid' + + 'PathLeaseUid': + 'description': > + The ID of a static lease. + 'example': 'abcd1234' + 'in': 'path' + 'name': 'lease_uid' + 'required': true + 'schema': + '$ref': '#/components/schemas/Uid' + + 'QueryBefore': + 'description': > + Unix time, before which to show the search results, in milliseconds. + 'example': 1614345496000 + 'in': 'query' + 'name': 'before' + 'required': false + 'schema': + 'format': 'double' + 'type': 'number' + + 'QueryClientId': + 'description': > + ClientID, **not** its UID. + 'example': 'client-1' + 'in': 'query' + 'name': 'client_id' + 'required': false + 'schema': + '$ref': '#/components/schemas/ClientId' + + 'QueryHost': + 'description': > + The host for which the Configuration is generated. + 'example': 'example.org' + 'in': 'query' + 'name': 'host' + 'required': true + 'schema': + 'type': 'string' + + 'QueryLimit': + 'description': > + Maximum amount of records to return. + 'example': 100 + 'in': 'query' + 'name': 'limit' + 'required': false + 'schema': + 'format': 'int64' + 'type': 'integer' + + 'QueryReason': + 'description': > + Filter query log results by filtering reason. + 'example': 'not_filtered_notfound' + 'in': 'query' + 'name': 'reason' + 'required': false + 'schema': + '$ref': '#/components/schemas/FilteringReason' + + 'QueryTerm': + 'description': > + Search term. + 'example': '127.0.0.1' + 'in': 'query' + 'name': 'term' + 'required': false + 'schema': + 'type': 'string' + + 'requestBodies': + 'PatchV1AccountsProfileReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1AccountsProfileReq' + 'required': true + + 'PatchV1ClientPersistentReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1ClientPersistentReq' + 'required': true + + 'PatchV1DhcpLeaseReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1DhcpLeaseReq' + 'required': true + + 'PatchV1ProtectionFilterReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1ProtectionFilterReq' + 'required': true + + 'PatchV1SettingsDhcpReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsDhcpReq' + 'required': true + + 'PatchV1SettingsDnsReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsDnsReq' + 'required': true + + 'PatchV1SettingsHttpReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsHttpReq' + 'required': true + + 'PatchV1SettingsLogReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsLogReq' + 'required': true + + 'PatchV1SettingsProtectionReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsProtectionReq' + 'required': true + + 'PatchV1SettingsStatsReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsStatsReq' + 'required': true + + 'PatchV1SettingsTlsReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsTlsReq' + 'required': true + + 'PostV1AccountsSessionReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1AccountsSessionReq' + 'required': true + + 'PostV1ClientsPersistentReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ClientsPersistentReq' + 'required': true + + 'PostV1DhcpLeasesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1DhcpLeasesReq' + 'required': true + + 'PostV1InstallCheckReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1InstallCheckReq' + 'required': true + + 'PostV1InstallConfigureReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1InstallConfigureReq' + 'required': true + + 'PostV1LogClearReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1LogClearReq' + 'required': true + + 'PostV1ProtectionCheckCustomRulesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionCheckCustomRulesReq' + 'required': true + + 'PostV1ProtectionDnsRewritesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionDnsRewritesReq' + 'required': true + + 'PostV1ProtectionFiltersReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionFiltersReq' + 'required': true + + 'PostV1ProtectionRefreshFilterReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionRefreshFilterReq' + 'required': true + + 'PostV1ProtectionRefreshFiltersReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionRefreshFiltersReq' + 'required': true + + 'PostV1SettingsDnsCheckReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SettingsDnsCheckReq' + 'required': true + + 'PostV1SettingsTlsCheckReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SettingsTlsCheckReq' + 'required': true + + 'PostV1StatsClearReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1StatsClearReq' + 'required': true + + 'PostV1SystemResetReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SystemResetReq' + 'required': true + + 'PostV1SystemUpdateReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SystemUpdateReq' + 'required': true + + 'PutV1ProtectionBlockedServicesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PutV1ProtectionBlockedServicesReq' + 'required': true + + 'PutV1ProtectionCustomRulesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PutV1ProtectionCustomRulesReq' + 'required': true + + 'PutV1SettingsDnsAccessReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PutV1SettingsDnsAccessReq' + 'required': true + + 'responses': + 'BadRequestResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/BadRequestResp' + 'text/plain': + 'example': >- + invalid character '{' looking for beginning of object key string + 'x-error-class': '#/components/schemas/BadRequestResp' + 'x-error-code': 'TXT400' + 'description': > + Generic bad request response. Sent when the request data is malformed + (for example, invalid JSON). + + 'GetV1AccountsProfileResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1AccountsProfileResp' + 'description': > + A successful response to a `GET /api/v1/accounts/profile` request. + + 'GetV1AppleDohMobileconfigResp': + 'content': + 'application/xml': + 'schema': + '$ref': '#/components/schemas/GetV1AppleDohMobileconfigResp' + 'description': > + A successful response to a `GET /api/v1/apple/doh.mobileconfig` request. + + 'GetV1AppleDotMobileconfigResp': + 'content': + 'application/xml': + 'schema': + '$ref': '#/components/schemas/GetV1AppleDotMobileconfigResp' + 'description': > + A successful response to a `GET /api/v1/apple/dot.mobileconfig` request. + + 'GetV1ClientsPersistentResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ClientsPersistentResp' + 'description': > + A successful response to a `GET /api/v1/clients/persistent` request. + + 'GetV1ClientsRuntimeResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ClientsRuntimeResp' + 'description': > + A successful response to a `GET /api/v1/clients/runtime` request. + + 'GetV1DhcpLeasesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1DhcpLeasesResp' + 'description': > + A successful response to a `GET /api/v1/dhcp/leases` request. + + 'GetV1DhcpStatusResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1DhcpStatusResp' + 'description': > + A successful response to a `GET /api/v1/dhcp/status` request. + + 'GetV1InstallInfoResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1InstallInfoResp' + 'description': > + A successful response to a `GET /api/v1/install/info` request. + + 'GetV1LogSearchResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1LogSearchResp' + 'description': > + A successful response to a `GET /api/v1/log/search` request. + + 'GetV1ProtectionBlockedServicesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ProtectionBlockedServicesResp' + 'description': > + A successful response to a `GET /api/v1/protection/blocked_services` + or a `PUT /api/v1/protection/blocked_services` request. + + 'GetV1ProtectionCustomRulesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ProtectionCustomRulesResp' + 'description': > + A successful response to a `GET /api/v1/protection/custom_rules` + request. + + 'GetV1ProtectionDnsRewritesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ProtectionDnsRewritesResp' + 'description': > + A successful response to a `GET /api/v1/protection/dns_rewrites` + request. + + 'GetV1ProtectionFiltersResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ProtectionFiltersResp' + 'description': > + A successful response to a `GET /api/v1/protection/filters` request. + + 'GetV1SettingsAllResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1SettingsAllResp' + 'description': > + A successful response to a `GET /api/v1/settings/all` request. + + 'GetV1SettingsDnsAccessResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1SettingsDnsAccessResp' + 'description': > + A successful response to a `GET /api/v1/settings/dns/access` request. + + 'GetV1StatsAllResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1StatsAllResp' + 'description': > + A successful response to a `GET /api/v1/stats/all` request. + + 'GetV1SystemInfoResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1SystemInfoResp' + 'description': > + A successful response to a `GET /api/v1/server/info` request. + + 'InternalServerErrorResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/InternalServerErrorResp' + 'text/plain': + 'example': >- + runtime error: invalid memory address or nil pointer dereference + 'x-error-class': '#/components/schemas/InternalServerErrorResp' + 'x-error-code': 'TXT500' + 'description': > + Generic internal server error. + + 'NoContentResp': + 'description': > + Generic no-error no-content response. + + 'NotFoundResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/NotFoundResp' + 'text/plain': + 'example': >- + Not found. + 'x-error-class': '#/components/schemas/NotFoundResp' + 'x-error-code': 'TXT404' + 'description': > + Generic not found response. + + 'PatchV1AccountsProfileResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1AccountsProfileResp' + 'description': > + A successful response to a `PATCH /api/v1/accounts/profile` request. + + 'PatchV1ClientPersistentResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1ClientPersistentResp' + 'description': > + A successful response to + a `PATCH /api/v1/clients/persistent/{client_uid}` request. + + 'PatchV1DhcpLeaseResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1DhcpLeaseResp' + 'description': > + A successful response to a `PATCH /api/v1/dhcp/leases/{lease_uid}` + request. + + 'PatchV1ProtectionFilterResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1ProtectionFilterResp' + 'description': > + A successful response to a `PATCH /api/v1/filters/{filter_uid}` request. + + 'PatchV1SettingsDhcpResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsDhcpResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/dhcp` request. + + 'PatchV1SettingsDnsResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsDnsResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/dns` request. + + 'PatchV1SettingsHttpResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsHttpResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/http` request. + + 'PatchV1SettingsLogResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsLogResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/log` request. + + 'PatchV1SettingsProtectionResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsProtectionResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/protection` request. + + 'PatchV1SettingsStatsResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsStatsResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/stats` request. + + 'PatchV1SettingsTlsResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsTlsResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/tls` request. + + 'PostV1ClientsPersistentResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ClientsPersistentResp' + 'description': > + A successful response to a `POST /api/v1/clients/persistent` request. + + 'PostV1DhcpLeasesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1DhcpLeasesResp' + 'description': > + A successful response to a `POST /api/v1/dhcp/leases` request. + + 'PostV1InstallCheckResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1InstallCheckResp' + 'description': > + A successful response to a `POST /api/v1/install/check` request. + + 'PostV1ProtectionCheckCustomRulesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionCheckCustomRulesResp' + 'description': > + A successful response to a `POST /api/v1/protection/check_custom_rules` + request. + + 'PostV1ProtectionDnsRewritesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionDnsRewritesResp' + 'description': > + A successful response to a `POST /api/v1/protection/dns_rewrites` + request. + + 'PostV1ProtectionFiltersResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionFiltersResp' + 'description': > + A successful response to a `POST /api/v1/protection/filters` request. + + 'PostV1ProtectionRefreshFilterResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionRefreshFilterResp' + 'description': > + A successful response to + a `POST /api/v1/protection/refresh_filters/{filter_uid}` request. + + 'PostV1ProtectionRefreshFiltersResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionRefreshFiltersResp' + 'description': > + A successful response to a `POST /api/v1/protection/refresh_filters` + request. + + 'PostV1SettingsDnsCheckResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SettingsDnsCheckResp' + 'description': > + A successful response to a `POST /api/v1/settings/dns/check` request. + + 'PostV1SettingsTlsCheckResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SettingsTlsCheckResp' + 'description': > + A successful response to a `POST /api/v1/settings/tls/check` request. + + 'PostV1SystemResetResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SystemResetResp' + 'description': > + A successful response to a `POST /api/v1/system/reset` request. + + 'PostV1SystemUpdateResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SystemUpdateResp' + 'description': > + A successful response to a `POST /api/v1/system/update` request. + + 'UnauthorizedResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/UnauthorizedResp' + 'text/plain': + 'example': 'no or bad authorization provided' + 'x-error-class': '#/components/schemas/UnauthorizedResp' + 'x-error-code': 'TXT401' + 'description': > + This API requires authorization. + 'headers': + 'WWW-Authenticate': + 'description': > + The required WWW-Authenticate header. + 'example': 'Basic realm="AdGuard Home", charset="UTF-8"' + 'required': true + 'schema': + 'type': 'string' + + 'UnprocessableEntityResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/UnprocessableEntityResp' + 'description': > + Generic bad request data response. Sent when the request data is + well-formed but is invalid for this request. + + 'schemas': + 'BadRequestResp': + 'example': + 'code': 'JSN000' + 'msg': >- + invalid character '{' looking for beginning of object key string + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'BlockedServiceId': + 'description': > + ID of a blocked service. + 'enum': + - '9gag' + - 'amazon' + - 'cloudflare' + - 'dailymotion' + - 'discord' + - 'disneyplus' + - 'ebay' + - 'epic_games' + - 'facebook' + - 'hulu' + - 'imgur' + - 'instagram' + - 'mail_ru' + - 'netflix' + - 'ok' + - 'origin' + - 'pinterest' + - 'qq' + - 'reddit' + - 'skype' + - 'snapchat' + - 'spotify' + - 'steam' + - 'telegram' + - 'tiktok' + - 'tinder' + - 'twitch' + - 'twitter' + - 'viber' + - 'vimeo' + - 'vk' + - 'wechat' + - 'weibo' + - 'whatsapp' + - 'youtube' + 'type': 'string' + + 'BlockedServices': + 'description': > + Blocked services. + 'example': + 'services': + - '9gag' + - 'dailymotion' + 'properties': + 'services': + 'description': > + All blocked services. + 'items': + '$ref': '#/components/schemas/BlockedServiceId' + 'type': 'array' + 'required': + - 'services' + 'type': 'object' + + 'Channel': + 'description': > + AdGuard Home release channel. + 'enum': + - 'beta' + - 'development' + - 'edge' + - 'release' + 'type': 'string' + + 'ClientId': + 'pattern': '[0-9a-z-]{1,64}' + 'type': 'string' + + 'ClientInfo': + 'description': > + A shorter information about a client. If the `uid` field is present, + this is a persistent client. Otherwise, this is a runtime client. + 'properties': + 'blocked': + 'description': > + If `true`, client is blocked. + 'type': 'boolean' + 'ids': + 'description': | + Client identifiers. That includes ClientIDs set by users as well as + IP addresses. There must be at least one identifier. + + Not to be confused with the `uid` field. + 'example': + - '1.2.3.4' + - 'user-1' + 'items': + 'type': 'string' + 'minItems': 1 + 'type': 'array' + 'name': + 'description': > + The name of the client, if any. If there are none, this field is + absent. + 'example': 'User 1' + 'type': 'string' + 'num': + 'description': > + Total number of requests for this client. + 'example': 1000 + 'format': 'int64' + 'type': 'integer' + 'num_blocked': + 'description': > + Total number of blocked requests for this client. + 'example': 1000 + 'format': 'int64' + 'type': 'integer' + 'uid': + '$ref': '#/components/schemas/Uid' + 'whois': + '$ref': '#/components/schemas/Whois' + 'required': + - 'blocked' + - 'ids' + - 'num' + - 'num_blocked' + 'type': 'object' + + 'CustomRules': + 'description': > + Custom filtering rules. + 'example': + 'rules': + - '||example.com' + - '# Some comment' + 'properties': + 'rules': + 'description': > + All custom filtering rules + 'items': + 'type': 'string' + 'type': 'array' + 'required': + - 'rules' + 'type': 'object' + + 'DhcpLease': + 'allOf': + - '$ref': '#/components/schemas/DhcpLeasePost' + - 'description': > + A dynamic or static DHCP lease. If the `uid` field is present, this is + a static lease. Otherwise, this is a dynamic lease. + 'example': + 'expires': 1614345496000 + 'hostname': 'my-mobile' + 'ip': '192.168.1.2' + 'mac': '01:23:45:67:89:ab' + 'uid': 'abcd1234' + 'properties': + 'uid': + '$ref': '#/components/schemas/Uid' + + 'DhcpLeasePatch': + 'description': > + A static DHCP lease update object. + 'example': + 'expires': 1614345496000 + 'properties': + 'expires': + 'description': > + The Unix time of the lease's expiry time, in milliseconds. + 'format': 'double' + 'type': 'number' + 'hostname': + 'description': > + Client's hostname. + 'type': 'string' + 'ip': + 'description': > + IP address leased to the client. + 'type': 'string' + 'mac': + 'description': > + Hardware address of the lease client. + 'type': 'string' + 'type': 'object' + + 'DhcpLeasePost': + 'allOf': + - '$ref': '#/components/schemas/DhcpLeasePatch' + - 'description': > + A static DHCP lease create object. + 'example': + 'expires': 1614345496000 + 'hostname': 'my-mobile' + 'ip': '192.168.1.2' + 'mac': '01:23:45:67:89:ab' + 'required': + - 'expires' + - 'hostname' + - 'ip' + - 'mac' + + 'DhcpSettings': + 'allOf': + - '$ref': '#/components/schemas/DhcpSettingsPatch' + - 'description': > + DHCP server settings. + 'example': + 'enabled': true + 'interface_name': 'wlan0' + 'ipv4_gateway_ip': '192.168.1.1' + 'ipv4_lease_duration': 86400000 + 'ipv4_range_end': '192.168.1.101' + 'ipv4_range_start': '192.168.1.2' + 'ipv4_subnet_mask': '255.255.255.0' + 'ipv6_range_start': '2001:db8::1' + 'ipv6_lease_duration': 86400000 + 'required': + - 'enabled' + + 'DhcpSettingsPatch': + 'description': > + DHCP server settings update object. + 'example': + 'enabled': true + 'interface_name': 'wlan0' + 'ipv4_gateway_ip': '192.168.1.1' + 'ipv4_lease_duration': 86400000 + 'ipv4_range_end': '192.168.1.101' + 'ipv4_range_start': '192.168.1.2' + 'ipv4_subnet_mask': '255.255.255.0' + 'properties': + 'enabled': + 'description': > + If `true`, the DHCP server is enabled. + 'type': 'boolean' + 'interface_name': + 'description': > + The name of network interface to serve on. + 'type': 'string' + 'ipv4_gateway_ip': + 'description': > + The IP address of the gateway. + 'type': 'string' + 'ipv4_lease_duration': + 'description': > + The duration of the IPv4 lease, in milliseconds. + 'type': 'number' + 'ipv4_range_end': + 'description': > + The end of the IPv4 addresses to serve to clients. + 'type': 'string' + 'ipv4_range_start': + 'description': > + The start of the IPv4 addresses to serve to clients. + 'type': 'string' + 'ipv4_subnet_mask': + 'description': > + The IP subnet mask. + 'type': 'string' + 'ipv6_lease_duration': + 'description': > + The duration of the IPv6 lease, in milliseconds. + 'type': 'number' + 'ipv6_range_start': + 'description': > + The start of the IPv6 addresses to serve to clients. + 'type': 'string' + 'type': 'object' + + 'DnsAccessSettings': + 'description': > + DNS server access settings. + 'example': + 'allowed_clients': [] + 'blocked_clients': + - '1.2.3.4' + - '5.6.7.8/16' + 'blocked_domain_rules': + - 'id.server' + - '*.example.org' + - '||example.com^' + 'properties': + 'allowed_clients': + 'description': > + CIDR or IP addresses of clients in the allowlist. If non-empty, + AdGuard Home will accept requests from these IP addresses only. + 'items': + 'type': 'string' + 'type': 'array' + 'blocked_clients': + 'description': > + CIDR or IP addresses of clients in the blocklist. If non-empty, + AdGuard Home will drop requests from these IP addresses. + 'items': + 'type': 'string' + 'type': 'array' + 'blocked_domain_rules': + 'description': > + AdGuard Home will drop DNS queries, if the domains in their queries + match these rules. Here you can specify the exact domain + names, wildcards, and `urlfilter` rules. Examples: + + * `example.org` + + * `*.example.org` + + * `||example.org^` + 'items': + 'type': 'string' + 'type': 'array' + 'required': + - 'allowed_clients' + - 'blocked_clients' + - 'blocked_domain_rules' + 'type': 'object' + + 'DnsBlockingMode': + 'description': | + DNS blocking mode. + + * `custom_ip`: Respond with a custom IP address. If this mode is + selected, both `blocking_ipv4` and `blocking_ipv6` parameters must + be set. + + * `default`: Same as `null_ip` for Adblock-style rules, but respond + with the IP address specified in the rule when blocked by an + `/etc/hosts`-style rule. + + * `null_ip`: Respond with a zero IP address: `0.0.0.0` for `A` + requests and `::` for `AAAA` ones. + + * `nxdomain`: Respond with the `NXDOMAIN` code. + + * `refused`: Respond with the `REFUSET` code. + + 'enum': + - 'custom_ip' + - 'default' + - 'null_ip' + - 'nxdomain' + - 'refused' + 'type': 'string' + + 'DnsClass': + 'description': > + DNS resource record class, aka `CLASS`. + 'enum': + - 'any' + - 'ch' + - 'cs' + - 'hs' + - 'in' + 'type': 'string' + + 'DnsProto': + 'description': > + DNS protocol. + 'enum': + - 'dot' + - 'doh' + - 'doq' + - 'dnscrypt' + - 'udp' + 'type': 'string' + + 'DnsResponseCode': + 'description': > + DNS response code, aka `RCODE`. + 'enum': + - 'badalg' + - 'badcookie' + - 'badkey' + - 'badmode' + - 'badname' + - 'badsig' + - 'badtime' + - 'badtrunc' + - 'badvers' + - 'formerr' + - 'noerror' + - 'notauth' + - 'notimp' + - 'notzone' + - 'nxdomain' + - 'nxrrset' + - 'refused' + - 'servfail' + - 'yxdomain' + - 'yxrrset' + 'type': 'string' + + 'DnsRewrite': + 'allOf': + - '$ref': '#/components/schemas/DnsRewritePost' + - 'description': > + A classic DNS rewrite. + 'example': + 'answer': 'A' + 'domain': 'example.com' + 'id': 'abcd1234' + 'properties': + 'id': + '$ref': '#/components/schemas/Uid' + 'required': + - 'answer' + - 'domain' + - 'id' + 'type': 'object' + + 'DnsRewritePost': + 'description': > + A classic DNS rewrite create object. + 'example': + 'answer': 'A' + 'domain': 'example.com' + 'properties': + 'answer': + 'description': > + The value of an `A`, `AAAA`, or `CNAME` DNS record in the response. + Acceptable formats: + + * Domain name: add a `CNAME` record with this domain name. + + * IPv4 address: use this IP in an `A` response. + + * IPv6 address: use this IP in an `AAAA` response. + + * The literal `A`: keep only `A` records from the upstream + response. + + * The literal `AAAA`: keep only `AAAA` records from the upstream + response. + 'type': 'string' + 'domain': + 'description': > + Domain name or wildcard. + 'type': 'string' + 'required': + - 'answer' + - 'domain' + 'type': 'object' + + 'DnsSettings': + 'allOf': + - '$ref': '#/components/schemas/DnsSettingsPatch' + - 'description': > + DNS server settings. + 'example': + 'addresses': + - '127.0.0.1:53' + - '192.168.1.1:53' + 'blocking_mode': 'default' + 'bootstrap_servers': + - '9.9.9.10' + - '149.112.112.10' + 'cache_size': 4194304 + 'cache_ttl_max': 0 + 'cache_ttl_min': 0 + 'dnssec': false + 'edns_client_subnet': false + 'ipv6': true + 'rate_limit': 20 + 'upstream_mode': 'load_balancing' + 'upstream_servers': + - '1.1.1.1' + - '8.8.8.8' + 'upstream_timeout': 1000 + 'required': + - 'addresses' + - 'blocking_mode' + - 'bootstrap_servers' + - 'cache_size' + - 'cache_ttl_max' + - 'cache_ttl_min' + - 'dnssec' + - 'edns_client_subnet' + - 'ipv6' + - 'rate_limit' + - 'upstream_mode' + - 'upstream_servers' + - 'upstream_timeout' + + 'DnsSettingsPatch': + 'description': > + DNS server settings update object. + 'example': + 'cache_size': 4194304 + 'upstream_servers': + - '1.1.1.1' + 'properties': + 'addresses': + 'description': > + Addresses on which to serve plain DNS, in ip:port format. Empty + array disables plain DNS. + 'items': + 'type': 'string' + 'type': 'array' + 'blocking_ipv4': + 'description': > + IPv4 address to respond with when `blocking_mode` is `custom_ip`. + See the documentation for the `DnsBlockingMode` schema. If + `blocking_mode` is different from `custom_ip`, this property is not + included. + 'type': 'string' + 'blocking_ipv6': + 'description': > + IPv6 address to respond with when `blocking_mode` is `custom_ip`. + See the documentation for the `DnsBlockingMode` schema. If + `blocking_mode` is different from `custom_ip`, this property is not + included. + 'type': 'string' + 'blocking_mode': + '$ref': '#/components/schemas/DnsBlockingMode' + 'bootstrap_servers': + 'description': | + Bootstrap DNS servers' IP addresses to resolve the hostnames of the + encrypted DNS server providers. + 'items': + 'type': 'string' + 'type': 'array' + 'cache_size': + 'description': > + DNS cache size in bytes. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'cache_ttl_max': + 'description': > + Set a maximum time-to-live value for entries in the DNS cache. `0` + means no override. The value is in **seconds**, like in DNS record + headers. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'cache_ttl_min': + 'description': > + Extend short time-to-live values received from the upstream server + when caching DNS responses. `0` means no override. TThe value is + in **seconds**, like in DNS record headers. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'dnssec': + 'description': > + If `true`, set DNSSEC flag in outcoming DNS queries and check the + result. A DNSSEC-enabled resolver is required. + 'type': 'boolean' + 'edns_client_subnet': + 'description': > + If `true`, enable EDNS Client Subnet support and send clients' + subnets to DNS servers. + 'type': 'boolean' + 'ipv6': + 'description': > + If `true`, accept `AAAA` DNS queries. If `false`, respond to them + with an empty answer. + 'type': 'boolean' + 'rate_limit': + 'description': > + The number of requests per second that a single client is allowed to + make. `0` means no limit. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'upstream_mode': + '$ref': '#/components/schemas/DnsUpstreamMode' + 'upstream_servers': + 'description': > + Upstream DNS servers. + 'items': + '$ref': '#/components/schemas/UpstreamServerAddr' + 'type': 'array' + 'upstream_timeout': + 'description': > + Upstream request timeout, in milliseconds. + 'format': 'double' + 'type': 'number' + 'type': 'object' + + 'DnsType': + 'description': > + DNS resource record type, aka `TYPE`. + 'enum': + - 'a' + - 'aaaa' + - 'afsdb' + - 'any' + - 'apl' + - 'atma' + - 'avc' + - 'axfr' + - 'caa' + - 'cdnskey' + - 'cds' + - 'cert' + - 'cname' + - 'csync' + - 'dhcid' + - 'dlv' + - 'dname' + - 'dnskey' + - 'ds' + - 'eid' + - 'eui48' + - 'eui64' + - 'gid' + - 'gpos' + - 'hinfo' + - 'hip' + - 'https' + - 'isdn' + - 'ixfr' + - 'key' + - 'kx' + - 'l32' + - 'l64' + - 'loc' + - 'lp' + - 'maila' + - 'mailb' + - 'mb' + - 'md' + - 'mf' + - 'mg' + - 'minfo' + - 'mr' + - 'mx' + - 'naptr' + - 'nid' + - 'nimloc' + - 'ninfo' + - 'ns' + - 'nsap-ptr' + - 'nsec' + - 'nsec3' + - 'nsec3param' + - 'null' + - 'nxt' + - 'openpgpkey' + - 'opt' + - 'ptr' + - 'px' + - 'rkey' + - 'rp' + - 'rrsig' + - 'rt' + - 'sig' + - 'smimea' + - 'soa' + - 'spf' + - 'srv' + - 'sshfp' + - 'svcb' + - 'ta' + - 'talink' + - 'tkey' + - 'tlsa' + - 'tsig' + - 'txt' + - 'uid' + - 'uinfo' + - 'unspec' + - 'uri' + - 'x25' + 'type': 'string' + + 'DnsUpstreamMode': + 'description': | + Upstream request mode. + + * `fastest`: Query all DNS servers and return the IP address that was + returned by the fastest response. Slows down DNS responses, since + it waits for responses from all upstreams, but improves the overall + connectivity. + + * `load_balancing`: Query one server at a time using a weighted random + algorithm picking the server so that the fastest server is used + more often. + + * `parallel`: Use parallel requests to speed up resolving by + simultaneously querying all upstream servers. + 'enum': + - 'fastest' + - 'load_balancing' + - 'parallel' + 'type': 'string' + + 'ErrorCode': + 'description': | + An error code. + + * `AUT000`: No or bad authorization credentials provided. + + * `ENT404`: Entity not found; as opposed to path not found. + + * `JSN000`: A JSON syntax error. + + * `JSN001`: A JSON type error. + + * `OSS000`: The server's operating system doesn't support the + requested functionality. + + * `PTH404`: Path not found; as opposed to entity not found. + + * `RNT000`: A server runtime error. + + * `TXT400`: A plaintext bad request error. Used when a plaintext + error is wrapped. + + * `TXT401`: A plaintext unauthorized error. Used when a plaintext + error is wrapped. + + * `TXT404`: A plaintext not found error. Used when a plaintext error + is wrapped. + + * `TXT500`: A plaintext internal server error. Used when a plaintext + error is wrapped. + + TODO(a.garipov): Expand with TLS validation errors, DHCP errors, filter + URL reaching errors, OS and I/O errors, and so on. + 'enum': + - 'AUT000' + - 'ENT404' + - 'JSN000' + - 'JSN001' + - 'OSS000' + - 'PTH404' + - 'RNT000' + - 'TXT400' + - 'TXT401' + - 'TXT404' + - 'TXT500' + 'type': 'string' + + 'Filter': + 'allOf': + - '$ref': '#/components/schemas/FilterPatch' + - 'description': > + A single filter list of rules. + 'example': + 'allowlist': false + 'enabled': true + 'name': 'AdMaster 5000 Super List v2.0 Final' + 'num_rules': 36766 + 'refreshed': 1614345496000 + 'uid': 'abcd1234' + 'url': 'https://admaster.example.com/list.txt' + 'properties': + 'num_rules': + 'description': > + Number of rules in this filter. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'refreshed': + 'description': > + Unix time of last refresh for this filter, in milliseconds. + 'format': 'double' + 'type': 'number' + 'uid': + '$ref': '#/components/schemas/Uid' + 'required': + - 'allowlist' + - 'enabled' + - 'name' + - 'num_rules' + - 'refreshed' + - 'uid' + - 'url' + + 'FilterPatch': + 'description': > + A filter update object. + 'example': + 'enabled': true + 'properties': + 'allowlist': + 'description': > + If `true`, this filter works as an allowlist filters. + 'type': 'boolean' + 'enabled': + 'description': > + If `true`, this filter is applied. + 'type': 'boolean' + 'name': + 'description': > + The name of this filter. + 'type': 'string' + 'url': + 'description': | + A URL of the file containing filtering rules. + + Examples of allowed schemes: + + * `file:///home/user/ads/rules.txt`: A local file. + + * `http://example.com/ads/rules.txt`: Remote list, fetched over + plain HTTP. + + * `https://example.com/ads/rules.txt`: Remote list, fetched over + HTTPS. + 'type': 'string' + 'type': 'object' + + 'FilterPost': + 'allOf': + - '$ref': '#/components/schemas/FilterPatch' + - 'description': > + A filter create object. + 'example': + 'allowlist': false + 'enabled': true + 'name': 'AdMaster 5000 Super List v2.0 Final' + 'url': 'https://admaster.example.com/list.txt' + 'required': + - 'allowlist' + - 'enabled' + - 'name' + - 'url' + + 'FilteringReason': + 'description': > + Request filtering status. + 'enum': + - 'filtered_blocked_service' + - 'filtered_blocklist' + - 'filtered_invalid' + - 'filtered_parental' + - 'filtered_safe_browsing' + - 'filtered_safe_search' + - 'not_filtered_allowlist' + - 'not_filtered_error' + - 'not_filtered_notfound' + - 'rewrite' + - 'rewrite_etc_hosts' + - 'rewrite_rule' + 'type': 'string' + + 'FilteringResultRule': + 'description': > + Applied filtering rule. + 'properties': + 'filter_list_uid': + '$ref': '#/components/schemas/Uid' + 'text': + 'description': > + The text of the filtering rule applied to the request, if any. + 'type': 'string' + 'required': + - 'filter_list_uid' + - 'text' + 'type': 'object' + + 'GetV1AccountsProfileResp': + '$ref': '#/components/schemas/Profile' + + # TODO(a.garipov): Find a way to describe such XML documents using OpenAPI. + # If that is even possible. + 'GetV1AppleDohMobileconfigResp': + 'example': | + + + + + PayloadContent + + + DNSSettings + + DNSProtocol + HTTPS + ServerName + example.com + ServerURL + https://example.com/dns-query/123 + + Name + myexample.local DoH + PayloadDescription + Configures device to use AdGuard Home + PayloadDisplayName + myexample.local DoH + PayloadIdentifier + com.apple.dnsSettings.managed.b6928468-ae3a-4368-a70d-cb7122275013 + PayloadType + com.apple.dnsSettings.managed + PayloadUUID + 18526b8c-6065-4b96-b635-9cde769ac0f2 + PayloadVersion + 1 + + + PayloadDescription + Adds AdGuard Home to Big Sur and iOS 14 or newer systems + PayloadDisplayName + myexample.local DoH + PayloadIdentifier + 9a37b659-7541-4f9e-8b4d-6e2a59a123c8 + PayloadRemovalDisallowed + + PayloadType + Configuration + PayloadUUID + 255dbaf7-0c52-4855-9b22-ad8209690197 + PayloadVersion + 1 + + + 'type': 'object' + + # TODO(a.garipov): See the comment on GetV1AppleDohMobileconfigResp. + 'GetV1AppleDotMobileconfigResp': + 'example': | + + + + + PayloadContent + + + DNSSettings + + DNSProtocol + TLS + ServerName + 123.example.com + + Name + example.com DoT + PayloadDescription + Configures device to use AdGuard Home + PayloadDisplayName + example.com DoT + PayloadIdentifier + com.apple.dnsSettings.managed.7807cb66-c6ec-4c78-be29-d8ffcb3321ee + PayloadType + com.apple.dnsSettings.managed + PayloadUUID + b0fb9137-e27a-4f95-abc3-556103ad9ac1 + PayloadVersion + 1 + + + PayloadDescription + Adds AdGuard Home to Big Sur and iOS 14 or newer systems + PayloadDisplayName + myexample.local DoT + PayloadIdentifier + f1095036-406e-4243-8210-cf0ffa52b3f6 + PayloadRemovalDisallowed + + PayloadType + Configuration + PayloadUUID + 21cd3597-0769-486a-86d0-7b5e32d24305 + PayloadVersion + 1 + + + 'type': 'object' + + 'GetV1ClientsPersistentResp': + 'description': > + Persistent clients. + 'example': + 'clients': + - 'blocked': false + 'blocked_services': [] + 'filtering': false + 'ids': ['client-1'] + 'name': 'Client 1' + 'parental': false + 'safe_browsing': false + 'safe_search': false + 'tags': ['user_admin'] + 'use_global_blocked_services': true + 'use_global_settings': true + 'uid': 'abcd1234' + 'upstream_servers': [] + - 'blocked': false + 'blocked_services': [] + 'filtering': true + 'ids': ['client-2'] + 'name': 'Client 2' + 'parental': true + 'safe_browsing': true + 'safe_search': true + 'tags': ['user_child'] + 'use_global_blocked_services': false + 'use_global_settings': false + 'uid': 'efgh5678' + 'upstream_servers': [] + 'properties': + 'clients': + 'description': > + All persistent clients. + 'items': + '$ref': '#/components/schemas/PersistentClient' + 'type': 'array' + 'required': + - 'clients' + 'type': 'object' + + 'GetV1ClientsRuntimeResp': + 'description': > + Runtime clients. + 'example': + 'clients': + - 'host': 'my-box' + 'ip': '1.2.3.4' + 'num_blocked_requests': 0 + 'num_requests': 100 + 'sources': + - 'arp' + - 'ip': '5.6.7.8' + 'num_blocked_requests': 100 + 'num_requests': 100 + 'sources': + - 'whois' + 'whois': + 'city': 'Minsk' + 'country': 'BY' + 'properties': + 'clients': + 'description': > + All runtime clients. + 'items': + '$ref': '#/components/schemas/RuntimeClient' + 'type': 'array' + 'required': + - 'clients' + 'type': 'object' + + 'GetV1DhcpLeasesResp': + 'description': > + All dynamic and static DHCP leases. + 'example': + 'leases': + - 'expires': 1614345496000 + 'hostname': 'my-mobile' + 'ip': '192.168.1.2' + 'mac': '01:23:45:67:89:ab' + 'uid': 'abcd1234' + - 'expires': 1614345497000 + 'hostname': '' + 'ip': '192.168.1.3' + 'mac': '01:23:45:67:89:cd' + 'properties': + 'leases': + 'description': > + Dynamic and static DHCP leases. + 'items': + '$ref': '#/components/schemas/DhcpLease' + 'type': 'array' + 'required': + - 'leases' + 'type': 'object' + + 'GetV1DhcpStatusResp': + 'description': > + Current DHCP server status and data for enabling it. + 'example': + 'interfaces': + - 'ips': + - '192.168.1.1' + 'mac': '01:23:45:67:89:ab' + 'mtu': 1500 + 'name': 'lan0' + 'up': true + 'ipv4_other_servers': + 'ips': + - '192.169.1.1' + 'ipv4_static_ip': + 'ip': '192.168.1.1' + 'static': true + 'supported': true + 'ipv6_other_servers': + 'ips': [] + 'error': 'permission denied' + 'ipv6_static_ip': + 'ip': '200f::1' + 'static': true + 'supported': true + 'properties': + 'interfaces': + 'description': > + Available network interfaces. + 'items': + '$ref': '#/components/schemas/NetworkInterface' + 'type': 'array' + 'ipv4_other_servers': + '$ref': '#/components/schemas/GetV1DhcpStatusRespOtherServer' + 'ipv4_static_ip': + '$ref': '#/components/schemas/StaticIpCheckResult' + 'ipv6_other_servers': + '$ref': '#/components/schemas/GetV1DhcpStatusRespOtherServer' + 'ipv6_static_ip': + '$ref': '#/components/schemas/StaticIpCheckResult' + 'required': + - 'interfaces' + - 'ipv4_other_servers' + - 'ipv4_static_ip' + - 'ipv6_other_servers' + - 'ipv6_static_ip' + 'type': 'object' + + 'GetV1DhcpStatusRespOtherServer': + 'properties': + 'error': + 'description': > + Error, if any. If there is no error, this field is absent. + 'type': 'string' + 'ips': + 'description': > + IP addresses of other DHCP servers, if found. + 'required': + - 'ips' + 'type': 'object' + + 'GetV1InstallInfoResp': + 'description': > + AdGuard Home addresses configuration. + 'example': + 'dns_port': 53 + 'interfaces': + - 'ips': + - '192.168.1.1' + 'mac': '01:23:45:67:89:ab' + 'mtu': 1500 + 'name': 'lan0' + 'up': true + 'web_port': 80 + 'properties': + 'dns_port': + 'description': > + Recommended DNS port. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'interfaces': + 'description': > + Available network interfaces. + 'items': + '$ref': '#/components/schemas/NetworkInterface' + 'type': 'array' + 'web_port': + 'description': > + Recommended web interface port. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'required': + - 'dns_port' + - 'interfaces' + - 'web_port' + 'type': 'object' + + 'GetV1LogSearchResp': + 'description': > + Query log search results. + 'example': + 'results': + - 'answer': + - 'ttl': 60 + 'type': 'a' + 'value': '5.6.7.8' + 'answer_dnssec': false + 'client': + 'blocked': false + 'ids': + - '1.2.3.4' + - 'user-1' + 'name': 'User 1' + 'num': 100 + 'num_blocked': 50 + 'uid': 'abcd1234' + 'whois': + 'city': 'Minsk' + 'country': 'BY' + 'elapsed': 3.2 + 'proto': 'udp' + 'question': + 'class': 'in' + 'host': 'example.com' + 'type': 'a' + 'rcode': 'noerror' + 'reason': 'not_filtered_notfound' + 'rules': [] + 'start': 1614345496000 + 'upstream': '8.8.8.8' + 'properties': + 'results': + 'description': > + The query log. + 'items': + '$ref': '#/components/schemas/LogRecord' + 'type': 'array' + 'required': + - 'results' + 'type': 'object' + + 'GetV1ProtectionBlockedServicesResp': + '$ref': '#/components/schemas/BlockedServices' + + 'GetV1ProtectionCustomRulesResp': + '$ref': '#/components/schemas/CustomRules' + + 'GetV1ProtectionDnsRewritesResp': + 'description': > + Classic DNS rewrites. + 'example': + 'rules': + - 'answer': 'A' + 'domain': 'example.com' + 'id': 'abcd1234' + - 'answer': '0.0.0.0' + 'domain': '*.example.org' + 'id': 'efgh5678' + - 'answer': 'my.example.net' + 'domain': 'example.net' + 'id': 'ijkl9012' + 'properties': + 'rules': + 'description': > + All classic DNS rewrites. + 'items': + '$ref': '#/components/schemas/DnsRewrite' + 'type': 'array' + 'required': + - 'rules' + 'type': 'object' + + 'GetV1ProtectionFiltersResp': + 'description': > + Filters. + 'example': + 'filters': + - 'allowlist': false + 'enabled': true + 'name': 'AdMaster 5000 Super List v2.0 Final' + 'num_rules': 36766 + 'refreshed': 1614345496000 + 'uid': 'abcd1234' + 'url': 'https://admaster.example.com/list.txt' + - 'allowlist': false + 'enabled': true + 'name': 'My personal list' + 'num_rules': 0 + 'refreshed': 1614345497000 + 'uid': 'efgh5678' + 'url': 'file:///home/user/Documents/ad_list.txt' + 'properties': + 'filters': + 'description': > + All current filters. + 'items': + '$ref': '#/components/schemas/Filter' + 'type': 'array' + 'required': + - 'filters' + 'type': 'object' + + # Perhaps a lot of these belong in separate APIs, but our colleagues asked + # to pack as much data into every request as reasonably possible. + 'GetV1SettingsAllResp': + 'description': > + Most settings. + # Don't add examples, as are provided by the subclasses. + 'properties': + 'dhcp': + '$ref': '#/components/schemas/DhcpSettings' + 'dns': + '$ref': '#/components/schemas/DnsSettings' + 'http': + '$ref': '#/components/schemas/HttpSettings' + 'log': + '$ref': '#/components/schemas/LogSettings' + 'protection': + '$ref': '#/components/schemas/ProtectionSettings' + 'stats': + '$ref': '#/components/schemas/StatsSettings' + 'tls': + '$ref': '#/components/schemas/TlsSettings' + 'required': + - 'dhcp' + - 'dns' + - 'http' + - 'log' + - 'protection' + - 'stats' + - 'tls' + 'type': 'object' + + 'GetV1SettingsDnsAccessResp': + '$ref': '#/components/schemas/DnsAccessSettings' + + # See the comment on the GetV1SettingsAllResp schema. + 'GetV1StatsAllResp': + 'description': > + All statistics. + 'example': + 'dns_cache_hit_rate': 56.7 + 'dns_cache_records': 123 + 'graph_avg_processing': + - 3.0 + - 0.4 + 'graph_blocked_ad_queries': + - 10 + - 20 + 'graph_blocked_custom_rule_queries': + - 10 + - 20 + 'graph_blocked_domains': + - 10 + - 20 + 'graph_blocked_parental_control_queries': + - 10 + - 20 + 'graph_blocked_safe_browsing_queries': + - 10 + - 20 + 'graph_blocked_safe_search_queries': + - 10 + - 20 + 'graph_blocked_service_queries': + - 10 + - 20 + 'graph_blocked_tracker_queries': + - 10 + - 20 + 'graph_cpu_percent': + - 50 + - 75 + 'graph_domains': + - 20 + - 30 + 'graph_queries': + - 1000 + - 2002 + 'graph_ram_resident': + - 1048576 + - 2097152 + 'time_unit': 'hour' + 'top_blocked_domains': + - 'name': 'example.net' + 'num': 100 + 'top_clients': + - 'blocked': false + 'ids': + - '1.2.3.4' + - 'user-1' + 'name': 'User 1' + 'num': 100 + 'num_blocked': 50 + 'uid': 'abcd1234' + 'whois': + 'city': 'Minsk' + 'country': 'BY' + - 'blocked': true + 'ids': + - '5.6.7.8' + 'num': 100 + 'num_blocked': 100 + 'top_domains': + - 'name': 'example.com' + 'num': 1000 + - 'name': 'example.net' + 'num': 100 + 'total_blocked_ad_queries': 100 + 'total_blocked_custom_rule_queries': 10 + 'total_blocked_domains': 500 + 'total_blocked_parental_control_queries': 10 + 'total_blocked_safe_browsing_queries': 10 + 'total_blocked_safe_search_queries': 10 + 'total_blocked_service_queries': 10 + 'total_blocked_tracker_queries': 10 + 'total_domains': 1000 + 'total_queries': 10000 + 'properties': + 'dns_cache_hit_rate': + 'description': > + DNS cache hit rate, in percent. + 'maximum': 100.0 + 'minimum': 0.0 + 'format': 'double' + 'type': 'number' + 'dns_cache_records': + 'description': > + Number of DNS responses currently in cache. + 'minimum': 0 + 'format': 'int64' + 'type': 'integer' + 'graph_avg_processing': + 'description': > + Average DNS query processing duration graph information. Each item + is one `time_unit` long. The duration is in milliseconds. Sorted + by time in descending order. + 'items': + 'format': 'double' + 'type': 'number' + 'type': 'array' + 'graph_blocked_ad_queries': + 'description': > + Number of queries blocked by advertising filters graph information. + Each item is one `time_unit` long. Sorted by time in descending + order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_custom_rule_queries': + 'description': > + Number of queries blocked by custom filtering rules graph + information. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_domains': + 'description': > + Blocked queried domains graph information. Each item is one + `time_unit` long. Sorted by time in descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_parental_control_queries': + 'description': > + Number of queries blocked by parental control services graph + information. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_safe_browsing_queries': + 'description': > + Number of queries blocked by safe browsing services graph + information. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_safe_search_queries': + 'description': > + Number of queries blocked by safe search services graph information. + Each item is one `time_unit` long. Sorted by time in descending + order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_service_queries': + 'description': > + Number of queries blocked by blocked service settings graph + information. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_tracker_queries': + 'description': > + Number of queries blocked by tracker filters graph information. + Each item is one `time_unit` long. Sorted by time in descending + order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_cpu_percent': + 'description': > + CPU usage percentage graph information. Each item is one + `time_unit` long. Sorted by time in descending order. + 'items': + 'format': 'double' + 'type': 'number' + 'type': 'array' + 'graph_domains': + 'description': > + Queried domains graph information. Each item is one `time_unit` + long. Sorted by time in descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_queries': + 'description': > + Number of served DNS queries graph information. Each item is one + `time_unit` long. Sorted by time in descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_ram_resident': + 'description': > + AdGuard Home's resident memory usage graph information. The size is + in bytes. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'time_unit': + '$ref': '#/components/schemas/TimeUnit' + 'top_blocked_domains': + 'description': > + Top blocked queried domains. Sorted by number in descending order. + 'items': + '$ref': '#/components/schemas/GetV1StatsAllRespTopsItem' + 'type': 'array' + 'top_clients': + 'description': > + Top clients. Sorted by number in descending order. + 'items': + '$ref': '#/components/schemas/ClientInfo' + 'type': 'array' + 'top_domains': + 'description': > + Top queried domains. Sorted by number in descending order. + 'items': + '$ref': '#/components/schemas/GetV1StatsAllRespTopsItem' + 'type': 'array' + 'total_blocked_ad_queries': + 'description': > + Total number of queries blocked by advertising filters. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_custom_rule_queries': + 'description': > + Total number of queries blocked by custom filtering rules. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_domains': + 'description': > + Total number of blocked queried domains. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_parental_control_queries': + 'description': > + Total number of queries blocked by parental control services. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_safe_browsing_queries': + 'description': > + Total number of queries blocked by safe browsing services. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_safe_search_queries': + 'description': > + Total number of queries blocked by safe search services. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_service_queries': + 'description': > + Total number of queries blocked by blocked service settings. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_tracker_queries': + 'description': > + Total number of queries blocked by tracker filters. + 'format': 'int64' + 'type': 'integer' + 'total_domains': + 'description': > + Total number of queried domains. + 'format': 'int64' + 'type': 'integer' + 'total_queries': + 'description': > + Total number of served DNS queries. + 'format': 'int64' + 'type': 'integer' + 'required': + - 'dns_cache_hit_rate' + - 'dns_cache_records' + - 'graph_avg_processing' + - 'graph_blocked_ad_queries' + - 'graph_blocked_custom_rule_queries' + - 'graph_blocked_domains' + - 'graph_blocked_parental_control_queries' + - 'graph_blocked_safe_browsing_queries' + - 'graph_blocked_safe_search_queries' + - 'graph_blocked_service_queries' + - 'graph_blocked_tracker_queries' + - 'graph_cpu_percent' + - 'graph_domains' + - 'graph_queries' + - 'graph_ram_resident' + - 'time_unit' + - 'top_blocked_domains' + - 'top_clients' + - 'top_domains' + - 'total_blocked_ad_queries' + - 'total_blocked_custom_rule_queries' + - 'total_blocked_domains' + - 'total_blocked_parental_control_queries' + - 'total_blocked_safe_browsing_queries' + - 'total_blocked_safe_search_queries' + - 'total_blocked_service_queries' + - 'total_blocked_tracker_queries' + - 'total_domains' + - 'total_queries' + 'type': 'object' + + 'GetV1StatsAllRespTopsItem': + 'description': > + A top array item. + 'properties': + 'name': + 'description': > + The name of the entity. Mostly domain names. + 'example': 'example.com' + 'type': 'string' + 'num': + 'description': > + The value of the statistic. + 'example': 1000 + 'format': 'int64' + 'type': 'integer' + 'required': + - 'name' + - 'num' + 'type': 'object' + + 'GetV1SystemInfoResp': + 'description': > + Information about the AdGuard Home server. + 'example': + 'arch': 'amd64' + 'channel': 'release' + 'new_version': 'v0.108.1' + 'os': 'linux' + 'start': 1614345496000 + 'version': 'v0.108.0' + 'properties': + 'arch': + 'description': > + CPU architecture. + 'type': 'string' + 'channel': + '$ref': '#/components/schemas/Channel' + 'new_version': + 'description': > + New available version of AdGuard Home to which the server can be + updated, if any. If there are none, this field is absent. + 'type': 'string' + 'os': + 'description': > + Operating system type. + 'type': 'string' + 'start': + 'description': > + Unix time at which AdGuard Home started working, in milliseconds. + 'format': 'double' + 'type': 'number' + 'version': + 'description': > + Current AdGuard Home version. + 'type': 'string' + 'required': + - 'arch' + - 'channel' + - 'os' + - 'start' + - 'version' + 'type': 'object' + + 'HttpSettings': + 'allOf': + - '$ref': '#/components/schemas/HttpSettingsPatch' + - 'description': > + HTTP interface server settings. + + **TODO(a.garipov): Finish, split from TLS settings.** + 'example': + 'addresses': + - '127.0.0.1:80' + - '192.168.1.1:80' + 'force_https': true + 'secure_addresses': + - '127.0.0.1:443' + - '192.168.1.1:443' + 'timeout': 10000 + 'required': + - 'addresses' + - 'force_https' + - 'secure_addresses' + - 'timeout' + + 'HttpSettingsPatch': + 'description': > + HTTP server settings update object. + 'example': + 'force_https': false + 'properties': + 'addresses': + 'description': > + Addresses on which to serve the plain-HTTP web interface and API, in + ip:port format. Empty array disables the web interface over plain + HTTP. + 'items': + 'type': 'string' + 'type': 'array' + 'force_https': + 'description': > + If `true`, enabled the HTTP-to-HTTPS redirect. + 'type': 'boolean' + 'secure_addresses': + 'description': > + Addresses on which to serve the HTTPS web interface and API, in + ip:port format. Empty array disables the web interface over HTTPS. + 'items': + 'type': 'string' + 'type': 'array' + 'timeout': + 'description': > + HTTP request timeout, in milliseconds. + 'format': 'double' + 'type': 'number' + 'type': 'object' + + 'InternalServerErrorResp': + 'example': + 'code': 'RNT000' + 'msg': >- + runtime error: invalid memory address or nil pointer dereference + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'Lang': + 'description': > + Language code. + # Hold the enum in sync with .twosky.json. + 'enum': + - 'be' + - 'bg' + - 'cs' + - 'da' + - 'de' + - 'en' + - 'es' + - 'fa' + - 'fr' + - 'hr' + - 'hu' + - 'id' + - 'it' + - 'ja' + - 'ko' + - 'nl' + - 'no' + - 'pl' + - 'pt-br' + - 'pt-pt' + - 'ro' + - 'ru' + - 'si-lk' + - 'sk' + - 'sl' + - 'sr-cs' + - 'sv' + - 'th' + - 'tr' + - 'vi' + - 'zh-cn' + - 'zh-hk' + - 'zh-tw' + 'type': 'string' + + 'LogRecord': + 'description': > + Query log record. + 'properties': + 'answer': + 'description': > + The answer given to the user. + 'items': + '$ref': '#/components/schemas/LogRecordDnsAnswer' + 'type': 'array' + 'answer_dnssec': + 'description': > + If `true`, DNSSEC was used. + 'type': 'boolean' + 'blocked_service': + 'description': > + Set if `reason` is `filtered_blocked_service`. Otherwise, this + field is absent. + 'type': 'string' + 'client': + '$ref': '#/components/schemas/ClientInfo' + 'elapsed': + 'description': > + Time it took to process the request, in milliseconds. + 'format': 'double' + 'type': 'number' + 'original_answer': + 'description': > + Original answer from the upstream server, if the answer was + rewritten. + 'items': + '$ref': '#/components/schemas/LogRecordDnsAnswer' + 'type': 'array' + 'proto': + '$ref': '#/components/schemas/DnsProto' + 'question': + '$ref': '#/components/schemas/LogRecordDnsQuestion' + 'rcode': + '$ref': '#/components/schemas/DnsResponseCode' + 'reason': + '$ref': '#/components/schemas/FilteringReason' + 'rules': + 'description': > + Applied rules. + 'items': + '$ref': '#/components/schemas/FilteringResultRule' + 'type': 'array' + 'start': + 'description': > + Request processing start Unix time, in milliseconds. + 'format': 'double' + 'type': 'number' + 'upstream': + '$ref': '#/components/schemas/UpstreamServerAddr' + 'required': + - 'answer' + - 'answer_dnssec' + - 'client' + - 'elapsed' + - 'proto' + - 'question' + - 'rcode' + - 'reason' + - 'rules' + - 'start' + - 'upstream' + 'type': 'object' + + 'LogRecordDnsAnswer': + 'description': > + DNS answer section. + 'properties': + 'ttl': + 'description': > + TTL of a record. This value is in **seconds**, like in DNS record + headers. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'type': + '$ref': '#/components/schemas/DnsType' + 'value': + 'description': > + An opaque string describing the result value. + 'type': 'string' + 'required': + - 'ttl' + - 'type' + - 'value' + 'type': 'object' + + 'LogRecordDnsQuestion': + 'description': > + DNS question section. + 'properties': + 'class': + '$ref': '#/components/schemas/DnsClass' + 'host': + 'description': > + Host from the query. + 'type': 'string' + 'type': + '$ref': '#/components/schemas/DnsType' + 'required': + - 'class' + - 'host' + - 'type' + 'type': 'object' + + 'LogSettings': + 'allOf': + - '$ref': '#/components/schemas/LogSettingsPatch' + - 'description': > + Query logging settings. + 'example': + 'anonymize': true + 'enabled': true + 'rotation': 604800000 + 'required': + - 'anonymize' + - 'enabled' + - 'rotation' + + 'LogSettingsPatch': + 'description': > + Query logging settings update object. + 'properties': + 'anonymize': + 'description': > + If `true`, client IP address anonymization is enabled. + 'type': 'boolean' + 'enabled': + 'description': > + If `true`, query logging is enabled. + 'type': 'boolean' + 'rotation': + 'description': > + Log rotation interval, in milliseconds. After that time, the log + file will be replaced by a new one, while the old one gets renamed. + 'format': 'double' + 'minimum': 86400000 + 'maximum': 7776000000 + 'type': 'number' + 'type': 'object' + + 'NetworkInterface': + 'properties': + 'ips': + 'description': > + The IP addresses of the interface, if any. + 'items': + 'type': 'string' + 'type': 'array' + 'mac': + 'description': > + The MAC address of the interface. + 'type': 'string' + 'mtu': + 'description': > + The interface's MTU, the maximum transmission unit. + 'format': 'int64' + 'type': 'integer' + 'name': + 'description': > + The name of the interface. + 'type': 'string' + 'up': + 'description': > + If `true`, the interface is up. + 'type': 'boolean' + 'required': + - 'ips' + - 'mac' + - 'mtu' + - 'name' + - 'up' + 'type': 'object' + + 'NotFoundResp': + 'example': + 'code': 'ENT404' + 'msg': >- + entity not found + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'PatchV1AccountsProfileReq': + 'example': + 'lang': 'ru' + 'properties': + 'lang': + '$ref': '#/components/schemas/Lang' + 'type': 'object' + + 'PatchV1AccountsProfileResp': + '$ref': '#/components/schemas/Profile' + + 'PatchV1ClientPersistentReq': + '$ref': '#/components/schemas/PersistentClientPatch' + + 'PatchV1ClientPersistentResp': + '$ref': '#/components/schemas/PersistentClient' + + 'PatchV1DhcpLeaseReq': + '$ref': '#/components/schemas/DhcpLeasePatch' + + 'PatchV1DhcpLeaseResp': + '$ref': '#/components/schemas/DhcpLease' + + 'PatchV1ProtectionFilterReq': + '$ref': '#/components/schemas/FilterPatch' + + 'PatchV1ProtectionFilterResp': + '$ref': '#/components/schemas/Filter' + + 'PatchV1SettingsDhcpReq': + '$ref': '#/components/schemas/DhcpSettingsPatch' + + 'PatchV1SettingsDhcpResp': + '$ref': '#/components/schemas/DhcpSettings' + + 'PatchV1SettingsDnsReq': + '$ref': '#/components/schemas/DnsSettingsPatch' + + 'PatchV1SettingsDnsResp': + '$ref': '#/components/schemas/DnsSettings' + + 'PatchV1SettingsHttpReq': + '$ref': '#/components/schemas/HttpSettingsPatch' + + 'PatchV1SettingsHttpResp': + '$ref': '#/components/schemas/HttpSettings' + + 'PatchV1SettingsLogReq': + '$ref': '#/components/schemas/LogSettingsPatch' + + 'PatchV1SettingsLogResp': + '$ref': '#/components/schemas/LogSettings' + + 'PatchV1SettingsProtectionReq': + '$ref': '#/components/schemas/ProtectionSettingsPatch' + + 'PatchV1SettingsProtectionResp': + '$ref': '#/components/schemas/ProtectionSettings' + + 'PatchV1SettingsStatsReq': + '$ref': '#/components/schemas/StatsSettingsPatch' + + 'PatchV1SettingsStatsResp': + '$ref': '#/components/schemas/StatsSettings' + + 'PatchV1SettingsTlsReq': + '$ref': '#/components/schemas/TlsSettingsPatch' + + 'PatchV1SettingsTlsResp': + '$ref': '#/components/schemas/TlsSettings' + + 'PersistentClient': + 'allOf': + - '$ref': '#/components/schemas/PersistentClientPatch' + - 'description': > + Persistent client. + 'example': + 'blocked': false + 'blocked_services': [] + 'filtering': false + 'ids': ['client-1'] + 'name': 'Client 1' + 'num_blocked_requests': 50 + 'num_requests': 100 + 'parental': false + 'safe_browsing': false + 'safe_search': false + 'tags': ['user_admin'] + 'use_global_blocked_services': true + 'use_global_settings': true + 'uid': 'abcd1234' + 'upstream_servers': [] + 'properties': + 'num_blocked_requests': + 'description': > + Total number of blocked requests for this runtime client. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'num_requests': + 'description': > + Total number of requests for this runtime client. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'uid': + '$ref': '#/components/schemas/Uid' + 'required': + - 'blocked' + - 'blocked_services' + - 'filtering' + - 'ids' + - 'name' + - 'parental' + - 'safe_browsing' + - 'safe_search' + - 'tags' + - 'uid' + - 'upstream_servers' + - 'use_global_blocked_services' + - 'use_global_settings' + + 'PersistentClientPatch': + 'description': > + Persistent client update object. + 'example': + 'filtering': false + 'parental': false + 'safe_browsing': false + 'safe_search': false + 'tags': ['user_admin'] + 'properties': + 'blocked': + 'description': > + If `true`, the client is blocked. + 'type': 'boolean' + 'blocked_services': + 'description': > + Custom blocked services for this client. + 'items': + '$ref': '#/components/schemas/BlockedServiceId' + 'type': 'array' + 'filtering': + 'description': > + If `true`, filtering based on filter rule lists is enabled for this + client. + 'type': 'boolean' + 'ids': + 'description': > + IP, CIDR, MAC, or ClientID (not to be confused with the `uid` field) + for client identification. + 'items': + 'type': 'string' + 'type': 'array' + 'name': + 'description': > + The name of this client. + 'type': 'string' + 'parental': + 'description': > + If `true`, parental protection is enabled for this client. + 'type': 'boolean' + 'safe_browsing': + 'description': > + If `true`, safe browsing protection is enabled for this client. + 'type': 'boolean' + 'safe_search': + 'description': > + If `true`, safe search protection is enabled for this client. + 'type': 'boolean' + 'tags': + 'description': > + Client tags. + 'items': + '$ref': '#/components/schemas/PersistentClientTag' + 'type': 'array' + 'use_global_blocked_services': + 'description': > + If `true`, use global blocked services for this client instead of + the custom ones. + 'type': 'boolean' + 'use_global_settings': + 'description': > + If `true`, use global protection settings for this client instead of + the custom ones. + 'type': 'boolean' + 'upstream_servers': + 'description': > + Custom upstream DNS servers for this client. + 'items': + '$ref': '#/components/schemas/UpstreamServerAddr' + 'type': 'array' + 'type': 'object' + + 'PersistentClientPost': + 'allOf': + - '$ref': '#/components/schemas/PersistentClientPatch' + - 'description': > + Persistent client create object. + 'example': + 'blocked': false + 'blocked_services': [] + 'filtering': false + 'ids': ['client-1'] + 'name': 'Client 1' + 'parental': false + 'safe_browsing': false + 'safe_search': false + 'tags': ['user_admin'] + 'use_global_blocked_services': true + 'use_global_settings': true + 'upstream_servers': [] + 'required': + - 'blocked' + - 'blocked_services' + - 'filtering' + - 'ids' + - 'name' + - 'parental' + - 'safe_browsing' + - 'safe_search' + - 'tags' + - 'upstream_servers' + - 'use_global_blocked_services' + - 'use_global_settings' + + 'PersistentClientTag': + 'description': > + Tags can be included in filtering rules to allow you to apply them more + accurately. + 'enum': + - 'device_audio' + - 'device_camera' + - 'device_gameconsole' + - 'device_laptop' + - 'device_nas' + - 'device_other' + - 'device_pc' + - 'device_phone' + - 'device_printer' + - 'device_securityalarm' + - 'device_tablet' + - 'device_tv' + - 'os_android' + - 'os_ios' + - 'os_linux' + - 'os_macos' + - 'os_other' + - 'os_windows' + - 'user_admin' + - 'user_child' + - 'user_regular' + 'type': 'string' + + 'PostV1AccountsSessionReq': + 'example': + 'password': 'G00dp455word!' + 'username': 'admin' + 'properties': + 'password': + 'description': > + Password. + 'format': 'password' + 'type': 'string' + 'username': + 'description': > + Username. + 'type': 'string' + 'required': + - 'password' + - 'username' + 'type': 'object' + + 'PostV1ClientsPersistentReq': + '$ref': '#/components/schemas/PersistentClientPost' + + 'PostV1ClientsPersistentResp': + '$ref': '#/components/schemas/PersistentClient' + + 'PostV1DhcpLeasesReq': + '$ref': '#/components/schemas/DhcpLeasePost' + + 'PostV1DhcpLeasesResp': + '$ref': '#/components/schemas/DhcpLease' + + 'PostV1InstallCheckReq': + 'description': > + Configuration for checking. + 'example': + 'dns': + 'ip': + - '0.0.0.0' + 'port': 53 + 'static_ip': false + 'web': + 'ip': + - '0.0.0.0' + 'port': 80 + 'properties': + 'dns': + '$ref': '#/components/schemas/PostV1InstallCheckReqServer' + 'static_ip': + 'description': > + If `true`, check if a static IP is set or can be set. + 'type': 'boolean' + 'web': + '$ref': '#/components/schemas/PostV1InstallCheckReqServer' + 'required': + - 'dns' + - 'static_ip' + - 'web' + 'type': 'object' + + 'PostV1InstallCheckReqServer': + 'description': > + A configuration for a server check. + 'properties': + 'ip': + 'description': > + IP addresses to check for availability. + 'items': + 'type': 'string' + 'minItems': 1 + 'type': 'array' + 'port': + 'description': > + Port to check for availability. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'required': + - 'ip' + - 'port' + 'type': 'object' + + 'PostV1InstallCheckResp': + 'description': > + Configuration checking response. + 'example': + 'dns': + 'error': 'permission denied' + 'static_ip': + 'ip': '192.168.1.1' + 'static': true + 'supported': true + 'web': {} + 'properties': + 'dns': + '$ref': '#/components/schemas/PostV1InstallCheckRespNetwork' + 'static_ip': + '$ref': '#/components/schemas/StaticIpCheckResult' + 'web': + '$ref': '#/components/schemas/PostV1InstallCheckRespNetwork' + 'required': + - 'dns' + - 'static_ip' + - 'web' + 'type': 'object' + + 'PostV1InstallCheckRespNetwork': + 'properties': + 'error': + 'description': > + Error, if any. If there is no error, this field is absent. + 'type': 'string' + 'type': 'object' + + 'PostV1InstallConfigureReq': + 'description': > + AdGuard Home initial configuration. + 'example': + 'dns_ip': '0.0.0.0' + 'dns_port': 53 + 'password': 'G00dp455word!' + 'username': 'admin' + 'set_static_ip': true + 'web_ip': '0.0.0.0' + 'web_port': 80 + 'properties': + 'dns_ip': + 'description': > + The IP address to serve DNS queries on. + 'type': 'string' + 'dns_port': + 'description': > + The port to serve DNS queries on. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'password': + 'description': > + Password. + 'type': 'string' + 'username': + 'description': > + Username. + 'type': 'string' + 'set_static_ip': + 'description': > + If `true`, set the server's IP address to static. + 'type': 'boolean' + 'web_ip': + 'description': > + The IP address to serve the web interface on. + 'type': 'string' + 'web_port': + 'description': > + The port to serve the web interface on. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'required': + - 'dns_ip' + - 'dns_port' + - 'password' + - 'username' + - 'set_static_ip' + - 'web_ip' + - 'web_port' + 'type': 'object' + + 'PostV1LogClearReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1ProtectionCheckCustomRulesReq': + 'description': > + Data to check using custom filtering rules. + 'example': + 'host': 'example.com' + 'properties': + 'host': + 'description': > + The hostname to check. + 'type': 'string' + 'required': + - 'host' + 'type': 'object' + + 'PostV1ProtectionCheckCustomRulesResp': + 'description': > + Custom filtering rules check results. + 'example': + 'reason': 'filtered_blocklist' + 'rules': + - 'filter_list_uid': 'abcd1234' + 'text': '||example.com^' + 'properties': + 'cname': + 'description': > + Set if `reason` is `Rewrite`. Otherwise, this field is absent. + 'type': 'string' + 'ip_addrs': + 'description': > + Set if `reason` is `Rewrite`. Otherwise, this field is absent. + 'items': + 'type': 'string' + 'type': 'array' + 'reason': + '$ref': '#/components/schemas/FilteringReason' + 'rules': + 'description': > + Applied rules. + 'items': + '$ref': '#/components/schemas/FilteringResultRule' + 'type': 'array' + 'service_name': + 'description': > + Set if `reason` is `FilteredBlockedService`. Otherwise, this field + is absent. + 'type': 'string' + 'required': + - 'reason' + - 'rules' + 'type': 'object' + + 'PostV1ProtectionDnsRewritesReq': + '$ref': '#/components/schemas/DnsRewritePost' + + 'PostV1ProtectionDnsRewritesResp': + '$ref': '#/components/schemas/DnsRewrite' + + 'PostV1ProtectionFiltersReq': + '$ref': '#/components/schemas/FilterPost' + + 'PostV1ProtectionFiltersResp': + '$ref': '#/components/schemas/Filter' + + 'PostV1ProtectionRefreshFilterReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1ProtectionRefreshFilterResp': + '$ref': '#/components/schemas/Filter' + + 'PostV1ProtectionRefreshFiltersReq': + 'description': > + Filters refresh parameters. + 'example': + 'allowlist': false + 'blocklist': true + 'properties': + 'allowlist': + 'description': > + If `true`, refresh all allowlist filters. + 'type': 'boolean' + 'blocklist': + 'description': > + If `true`, refresh all blocklist filters. + 'type': 'boolean' + 'required': + - 'allowlist' + - 'blocklist' + 'type': 'object' + + 'PostV1ProtectionRefreshFiltersResp': + 'description': > + Refresh results. + 'example': + 'errors': + - 'msg': 'context deadline exceeded' + 'uid': 'efgh5678' + 'refreshed': + - 'allowlist': false + 'enabled': true + 'name': 'AdMaster 5000 Super List v2.0 Final' + 'num_rules': 36766 + 'refreshed': 1614345496000 + 'uid': 'abcd1234' + 'url': 'https://admaster.example.com/list.txt' + 'properties': + 'errors': + 'description': > + All encountered errors. + 'items': + '$ref': '#/components/schemas/RefreshFilterError' + 'type': 'array' + 'refreshed': + 'description': > + Refreshed filters. + 'items': + '$ref': '#/components/schemas/Filter' + 'type': 'array' + 'required': + - 'errors' + - 'refreshed' + 'type': 'object' + + 'PostV1SettingsDnsCheckReq': + 'description': > + Validatable DNS settings. + 'example': + 'bootstrap_servers': + - '9.9.9.10' + - '149.112.112.10' + 'upstream_servers': + - '1.1.1.1' + - '8.8.8.8' + 'properties': + 'bootstrap_servers': + 'description': | + Bootstrap DNS servers' IP addresses to check. + 'items': + 'type': 'string' + 'type': 'array' + 'upstream_servers': + 'description': > + Upstream DNS servers to check. + 'items': + '$ref': '#/components/schemas/UpstreamServerAddr' + 'type': 'array' + 'required': + - 'bootstrap_servers' + - 'upstream_servers' + 'type': 'object' + + 'PostV1SettingsDnsCheckResp': + 'description': > + DNS settings validation results. + 'example': + 'bootstrap_servers': + '9.9.9.10': 'network is unreachable' + 'upstream_servers': + '8.8.8.8': 'network is unreachable' + 'properties': + 'bootstrap_servers': + 'additionalProperties': + 'minLength': 1 + 'type': 'string' + 'description': > + An IP-address-to-error mapping. If an address is not in this + object, the check for that address is successful. If there were no + errors, this field is absent. + 'upstream_servers': + 'additionalProperties': + 'type': 'string' + 'description': > + An upstream-address-to-error mapping. If an address is not in this + object, the check for that address is successful. If there were no + errors, this field is absent. + 'type': 'object' + + 'PostV1SettingsTlsCheckReq': + 'description': > + Validatable TLS settings. + 'example': + 'certificate_path': '/etc/ssl/example.com.cert' + 'port_dns_over_quic': 853 + 'port_dns_over_tls': 853 + 'port_https': 443 + 'private_key_path': '/etc/ssl/example.com.key' + 'server_name': 'dns.example.com' + 'properties': + 'certificate': + 'description': | + Base64-encoded string with PEM-encoded certificate chain. + + Should not be sent if `certificate_path` is sent. Otherwise, must + be sent. + 'format': 'byte' + 'type': 'string' + 'certificate_path': + 'description': | + Path to the certificate file. + + Should not be sent if `certificate` is sent. Otherwise, must be + sent. + 'type': 'string' + 'port_dns_over_quic': + 'default': 853 + 'description': > + The DNS-over-QUIC port. If `0`, DNS-over-QUIC is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'port_dns_over_tls': + 'default': 853 + 'description': > + The DNS-over-TLS port. If `0`, DNS-over-TLS is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'port_https': + 'default': 443 + 'description': > + The HTTPS port. If `0`, HTTPS is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'private_key': + 'description': | + Base64-encoded string with PEM-encoded private key. + + Should not be sent if `private_key_path` is sent. Otherwise, must + be sent. + 'format': 'byte' + 'type': 'string' + 'private_key_path': + 'description': | + Path to the private key file. + + Should not be sent if `private_key` is sent. Otherwise, must be + sent. + 'type': 'string' + 'server_name': + 'description': > + The name of the server. Used to validate the certificates as well + as to check ClientIDs in DNS-over-HTTP and DNS-over-TLS. + 'type': 'string' + 'required': + - 'port_dns_over_quic' + - 'port_dns_over_tls' + - 'port_https' + - 'server_name' + 'type': 'object' + + 'PostV1SettingsTlsCheckResp': + 'description': > + TLS settings validation results. + 'example': + 'dns_names': + - '*.example.com' + - 'example.com' + 'issuer': 'CN=Example CA,OU=Development,O=Example CA,L=Canberra,ST=Canberra,C=AU' + 'key_type': 'RSA' + 'not_after': 1614345497000 + 'not_before': 1614345496000 + 'port_https_error': 'address already in use' + 'subject': 'CN=Example CA,OU=Development,O=Example CA,L=Canberra,ST=Canberra,C=AU' + 'warnings': [] + 'properties': + 'cert_error': + 'description': > + Certificate validation error, if any. If the certificate is valid, + this field is absent. + 'type': 'string' + 'chain_error': + 'description': > + Certificate chain validation error, if any. If the certificate + chain is valid, this field is absent. + 'type': 'string' + 'dns_names': + 'description': > + The value of the `SubjectAltNames` field of the first certificate in + the chain. + 'items': + 'type': 'string' + 'type': 'array' + 'issuer': + 'description': > + The issuer of the first certificate in the chain. + 'type': 'string' + 'key_error': + 'description': > + Private key pair error, if any. If the key is valid, this field is + absent. + 'type': 'string' + 'key_type': + '$ref': '#/components/schemas/TlsKeyType' + 'not_after': + 'description': > + The value of the `NotAfter` field of the first certificate in the + chain, as a Unix time, in milliseconds. + 'format': 'double' + 'type': 'number' + 'not_before': + 'description': > + The value of the `NotBefore` field of the first certificate in the + chain, as a Unix time, in milliseconds. + 'format': 'double' + 'type': 'number' + 'port_dns_over_quic_error': + 'description': > + DNS-over-QUIC port checking error, if any. If the port is + available, this field is absent. + 'type': 'string' + 'port_dns_over_tls_error': + 'description': > + DNS-over-TLS port checking error, if any. If the port is available, + this field is absent. + 'type': 'string' + 'port_https_error': + 'description': > + DNS-over-HTTPS port checking error, if any. If the port is + available, this field is absent. + 'type': 'string' + 'pair_error': + 'description': > + Certificate and key pair error, if any. If the pair is valid, this + field is absent. + 'type': 'string' + 'subject': + 'description': > + The subject of the first certificate in the chain. + 'type': 'string' + 'warnings': + 'description': > + Validation warnings, if any. + 'items': + 'type': 'string' + 'type': 'array' + 'required': + - 'dns_names' + - 'issuer' + - 'key_type' + - 'not_after' + - 'not_before' + - 'subject' + - 'warnings' + 'type': 'object' + + 'PostV1StatsClearReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1SystemResetReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1SystemResetResp': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1SystemUpdateReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1SystemUpdateResp': + 'example': + 'reload': 10000 + 'properties': + 'reload': + 'description': > + Time, after which the frontend must reload the page, in + milliseconds. + 'format': 'double' + 'type': 'number' + 'type': 'object' + + 'Profile': + 'description': > + Current user's profile. + 'example': + 'lang': 'en' + 'username': 'admin' + 'properties': + 'lang': + '$ref': '#/components/schemas/Lang' + 'username': + 'description': > + Current user's name. + 'type': 'string' + 'required': + - 'lang' + - 'username' + 'type': 'object' + + 'ProtectionSettings': + 'allOf': + - '$ref': '#/components/schemas/ProtectionSettingsPatch' + - 'description': > + Protection settings. + 'example': + 'autoupdate': 86400000 + 'filtering': true + 'parental': true + 'safe_browsing': false + 'safe_search': false + 'required': + - 'autoupdate' + - 'filtering' + - 'parental' + - 'safe_browsing' + - 'safe_search' + + 'ProtectionSettingsPatch': + 'description': > + Protection settings update object. + 'example': + 'autoupdate': 0 + 'properties': + 'autoupdate': + 'description': > + Filter automatic update interval, in milliseconds. Set to `0` to + disable automatic updates. + 'format': 'double' + 'minimum': 0 + 'maximum': 604800000 + 'type': 'number' + 'filtering': + 'description': > + If `true`, filtering based on filter rule lists is enabled. + 'type': 'boolean' + 'parental': + 'description': > + If `true`, parental protection is enabled. + 'type': 'boolean' + 'pause_end': + 'description': | + If `state` is `paused`, `pause_end` will show the Unix time until + which the protection is disabled in milliseconds. Otherwise, the + property won't be set. + + When updating, if `state` is set to `paused`, `pause_end` must be + set to a timestamp in the future. + 'format': 'double' + 'type': 'number' + 'safe_browsing': + 'description': > + If `true`, safe browsing protection is enabled. + 'type': 'boolean' + 'safe_search': + 'description': > + If `true`, safe search protection is enabled. + 'type': 'boolean' + 'state': + '$ref': '#/components/schemas/ProtectionSettingsState' + 'type': 'object' + + 'ProtectionSettingsState': + 'description': | + State of protection. + + * `off`: Protection is disabled. + + * `on`: Protection is enabled. + + * `paused`: Protection is paused. See the `pause_end` property to get + or set the end of the pause. + 'enum': + - 'off' + - 'on' + - 'paused' + 'type': 'string' + + 'PutV1ProtectionBlockedServicesReq': + '$ref': '#/components/schemas/BlockedServices' + + 'PutV1ProtectionCustomRulesReq': + '$ref': '#/components/schemas/CustomRules' + + 'PutV1SettingsDnsAccessReq': + '$ref': '#/components/schemas/DnsAccessSettings' + + 'RefreshFilterError': + 'description': > + Filter refresh error. + 'properties': + 'msg': + 'description': > + Error message. + 'type': 'string' + 'uid': + '$ref': '#/components/schemas/Uid' + 'required': + - 'msg' + - 'uid' + 'type': 'object' + + 'RuntimeClient': + 'description': > + A runtime client's information. + 'properties': + 'host': + 'description': > + The RDNS host of the runtime, if any. If there is none, this field + is absent. + 'type': 'string' + 'ip': + 'description': > + The IP-address of the runtime client. + 'type': 'string' + 'sources': + 'description': > + The sources from which the information about this runtime client was + collected. + 'items': + '$ref': '#/components/schemas/RuntimeClientSource' + 'minItems': 1 + 'type': 'array' + 'num_blocked_requests': + 'description': > + Total number of blocked requests for this runtime client. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'num_requests': + 'description': > + Total number of requests for this runtime client. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'whois': + '$ref': '#/components/schemas/Whois' + 'required': + - 'ip' + - 'num_blocked_requests' + - 'num_requests' + - 'sources' + 'type': 'object' + + 'RuntimeClientSource': + 'description': > + The source from which the information about this runtime client was + collected. + + * `arp`: The information was collected from the `arp -a` output. + + * `dhcp`: The information was collected from our DHCP server. + + * `hosts_file`: The information was collected from the `/etc/hosts` + file. + + * `rdns`: The information was collected by performing a reverse DNS + lookup. + + * `whois`: The information was collected by performing a WHOIS lookup. + 'enum': + - 'arp' + - 'dhcp' + - 'hosts_file' + - 'rdns' + - 'whois' + 'type': 'string' + + 'StaticIpCheckResult': + 'properties': + 'error': + 'description': > + Error, if any. If there is no error, this field is absent. + 'type': 'string' + 'ip': + 'description': > + The IP address. + 'type': 'string' + 'static': + 'description': > + If `true`, the interface has a static IP address. + 'type': 'boolean' + 'supported': + 'description': > + If `true`, setting a static IP on this system is supported. + 'type': 'boolean' + 'required': + - 'ip' + - 'static' + - 'supported' + 'type': 'object' + + 'StatsSettings': + 'allOf': + - '$ref': '#/components/schemas/StatsSettingsPatch' + - 'description': > + Statistics settings. + 'required': + - 'autorefresh' + - 'retention' + + 'StatsSettingsPatch': + 'description': > + Statistics settings update object. + 'properties': + 'autorefresh': + 'description': > + Statistics UI autorefresh time in milliseconds. `0` means + autorefresh is disabled. + 'format': 'double' + 'type': 'number' + 'retention': + 'description': > + Statistics retention interval, in milliseconds. + 'format': 'double' + 'type': 'number' + 'type': 'object' + + 'TimeUnit': + 'description': > + Time units used for statistics. See the documentation for the + `GET /api/v1/stats/all` request. + 'enum': + - 'hour' + - 'day' + 'type': 'string' + + 'TlsKeyType': + 'description': > + TLS key type. + 'enum': + - 'ECDSA' + - 'RSA' + 'type': 'string' + + 'TlsSettings': + 'allOf': + - '$ref': '#/components/schemas/TlsSettingsPatch' + - 'description': > + TLS and encryption settings. + 'example': + 'certificate_path': '/etc/ssl/example.com.cert' + 'enabled': true + 'port_dns_over_quic': 853 + 'port_dns_over_tls': 853 + 'port_https': 443 + 'private_key_path': '/etc/ssl/example.com.key' + 'server_name': 'dns.example.com' + 'required': + - 'enabled' + - 'port_dns_over_quic' + - 'port_dns_over_tls' + - 'port_https' + - 'server_name' + + 'TlsSettingsPatch': + 'description': > + TLS and encryption settings update object. + 'example': + 'certificate': 'Base64KeyDatAA==' + 'enabled': true + 'private_key': 'Base64CertDatA==' + 'properties': + 'certificate': + 'description': | + Base64-encoded string with PEM-encoded certificate chain. + + Should not be sent if `certificate_path` is sent. Otherwise, must + be sent. + 'format': 'byte' + 'type': 'string' + 'certificate_path': + 'description': | + Path to the certificate file. + + Should not be sent if `certificate` is sent. Otherwise, must be + sent. + 'type': 'string' + 'enabled': + 'description': > + If `true`, AdGuard Home the administration interface will be served + over HTTPS, and the DNS server will listen requests over + DNS-over-TLS and other protocols. + 'type': 'boolean' + 'port_dns_over_quic': + 'default': 853 + 'description': > + The DNS-over-QUIC port. If `0`, DNS-over-QUIC is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'port_dns_over_tls': + 'default': 853 + 'description': > + The DNS-over-TLS port. If `0`, DNS-over-TLS is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'port_https': + 'default': 443 + 'description': > + The HTTPS port. If `0`, HTTPS is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'private_key': + 'description': | + Base64-encoded string with PEM-encoded private key. + + Should not be sent if `private_key_path` is sent. Otherwise, must + be sent. + 'format': 'byte' + 'type': 'string' + 'private_key_path': + 'description': | + Path to the private key file. + + Should not be sent if `private_key` is sent. Otherwise, must be + sent. + 'type': 'string' + 'server_name': + 'description': > + The name of the server. Used to validate the certificates as well + as to check ClientIDs in DNS-over-HTTP and DNS-over-TLS. + 'type': 'string' + 'type': 'object' + + 'Uid': + 'description': > + A unique ID of an entity, an opaque string. + 'pattern': '[0-9a-zA-Z_-]{1,64}' + 'type': 'string' + + 'UnauthorizedResp': + 'example': + 'code': 'AUT000' + 'msg': 'no or bad authorization provided' + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'UnprocessableEntityResp': + 'example': + 'code': 'JSN001' + 'msg': >- + json: cannot unmarshal string into Go struct field T.A of type int + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'UpstreamServerAddr': + 'description': | + Upstream DNS server address. Supported item formats: + + * `94.140.14.140`: plain DNS-over-UDP. + + * `tls://unfiltered.adguard-dns.com`: encrypted DNS-over-TLS. + + * `https://unfiltered.adguard-dns.com/dns-query`: encrypted + DNS-over-HTTPS. + + * `quic://unfiltered.adguard-dns.com`: encrypted DNS-over-QUIC. + + * `tcp://94.140.14.140`: plain DNS-over-TCP. + + * `sdns://...`: DNS Stamps for DNSCrypt or DNS-over-HTTPS + resolvers. + + * `[/example.local/]94.140.14.140`: DNS upstream for specific + domain(s). + + * `# comment`: A comment. + 'type': 'string' + + 'Whois': + 'additionalProperties': + 'type': 'string' + 'description': > + WHOIS information, if any. If there are none, this field is usually + absent. + 'minProperties': 1 + 'type': 'object' + + # TODO(a.garipov): Find a way to specify a cookie authorization. + 'securitySchemes': + 'basicAuth': + 'description': > + Basic HTTP authorization. + 'scheme': 'basic' + 'type': 'http' diff --git a/scripts/blocked-services/main.go b/scripts/blocked-services/main.go index ac6477b5..5e57860a 100644 --- a/scripts/blocked-services/main.go +++ b/scripts/blocked-services/main.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "os" + "strings" "text/template" "time" @@ -43,8 +44,8 @@ func main() { check(err) // Sort all services and rules to make the output more predictable. - slices.SortStableFunc(hlSvcs.BlockedServices, func(a, b *hlServicesService) (less bool) { - return a.ID < b.ID + slices.SortStableFunc(hlSvcs.BlockedServices, func(a, b *hlServicesService) (res int) { + return strings.Compare(a.ID, b.ID) }) for _, s := range hlSvcs.BlockedServices { slices.Sort(s.Rules) diff --git a/scripts/install.sh b/scripts/install.sh index 8c30268d..08c54e87 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -468,7 +468,7 @@ rerun_with_root() { readonly r u v log 'restarting with root privileges' - + # Group curl/wget together with an echo, so that if the former fails before # producing any output, the latter prints an exit command for the following # shell to execute to prevent it from getting an empty input and exiting diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 458a68cf..e89eca6d 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -3,7 +3,7 @@ # This comment is used to simplify checking local copies of the script. Bump # this number every time a significant change is made to this script. # -# AdGuard-Project-Version: 4 +# AdGuard-Project-Version: 5 verbose="${VERBOSE:-0}" readonly verbose @@ -35,7 +35,7 @@ set -f -u go_version="$( "${GO:-go}" version )" readonly go_version -go_min_version='go1.20.7' +go_min_version='go1.20.8' go_version_msg=" warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}). if you have the version installed, please set the GO environment variable. @@ -170,36 +170,62 @@ run_linter govulncheck ./... run_linter gocyclo --over 10 . -# TODO(a.garipov): Enable for all. -run_linter gocognit --over 10\ +# TODO(a.garipov): Enable 10 for all. +run_linter gocognit --over='20'\ + ./internal/querylog/\ + ; + +run_linter gocognit --over='19'\ + ./internal/home/\ + ; + +run_linter gocognit --over='18'\ + ./internal/aghtls/\ + ; + +run_linter gocognit --over='15'\ + ./internal/aghos/\ + ./internal/dnsforward/\ + ./internal/filtering/\ + ; + +run_linter gocognit --over='14'\ + ./internal/dhcpd\ + ; + +run_linter gocognit --over='13'\ + ./internal/aghnet/\ + ; + +run_linter gocognit --over='12'\ + ./internal/updater/\ + ./internal/filtering/rewrite/\ + ; + +run_linter gocognit --over='10'\ ./internal/aghalg/\ ./internal/aghchan/\ ./internal/aghhttp/\ ./internal/aghio/\ ./internal/aghrenameio/\ + ./internal/aghtest/\ + ./internal/arpdb/\ ./internal/client/\ + ./internal/confmigrate/\ ./internal/dhcpsvc\ ./internal/filtering/hashprefix/\ ./internal/filtering/rulelist/\ + ./internal/filtering/safesearch/\ ./internal/next/\ ./internal/rdns/\ ./internal/schedule/\ + ./internal/stats/\ ./internal/tools/\ ./internal/version/\ ./internal/whois/\ ./scripts/\ ; -# TODO(a.garipov): move these to the group above. -run_linter gocognit --over 20 ./internal/aghnet/ ./internal/querylog/ -run_linter gocognit --over 19 ./internal/dnsforward/ ./internal/home/ -run_linter gocognit --over 18 ./internal/aghtls/ -run_linter gocognit --over 17 ./internal/filtering ./internal/filtering/rewrite/ -run_linter gocognit --over 15 ./internal/aghos/ ./internal/dhcpd/ -run_linter gocognit --over 14 ./internal/stats/ -run_linter gocognit --over 12 ./internal/updater/ -run_linter gocognit --over 11 ./internal/aghtest/ - run_linter ineffassign ./... run_linter unparam ./... @@ -212,33 +238,62 @@ run_linter looppointer ./... run_linter nilness ./... -# TODO(a.garipov): Add fieldalignment? +# TODO(a.garipov): Enable for all. +run_linter fieldalignment \ + ./internal/aghalg/\ + ./internal/aghchan/\ + ./internal/aghhttp/\ + ./internal/aghio/\ + ./internal/aghos/\ + ./internal/aghrenameio/\ + ./internal/aghtest/\ + ./internal/aghtls/\ + ./internal/arpdb/\ + ./internal/client/\ + ./internal/confmigrate/\ + ./internal/dhcpsvc/\ + ./internal/filtering/hashprefix/\ + ./internal/filtering/rewrite/\ + ./internal/filtering/rulelist/\ + ./internal/filtering/safesearch/\ + ./internal/next/...\ + ./internal/querylog/\ + ./internal/rdns/\ + ./internal/stats/\ + ./internal/updater/\ + ./internal/version/\ + ./internal/whois/\ + ; run_linter -e shadow --strict ./... # TODO(a.garipov): Enable for all. run_linter gosec --quiet\ - ./internal/aghalg\ - ./internal/aghchan\ - ./internal/aghhttp\ - ./internal/aghio\ - ./internal/aghnet\ - ./internal/aghos\ + ./internal/aghalg/\ + ./internal/aghchan/\ + ./internal/aghhttp/\ + ./internal/aghio/\ + ./internal/aghnet/\ + ./internal/aghos/\ ./internal/aghrenameio/\ - ./internal/aghtest\ - ./internal/client\ - ./internal/dhcpd\ - ./internal/dhcpsvc\ - ./internal/dnsforward\ + ./internal/aghtest/\ + ./internal/arpdb/\ + ./internal/client/\ + ./internal/confmigrate/\ + ./internal/dhcpd/\ + ./internal/dhcpsvc/\ + ./internal/dnsforward/\ ./internal/filtering/hashprefix/\ + ./internal/filtering/rewrite/\ ./internal/filtering/rulelist/\ - ./internal/next\ - ./internal/rdns\ - ./internal/schedule\ - ./internal/stats\ - ./internal/tools\ - ./internal/version\ - ./internal/whois\ + ./internal/filtering/safesearch/\ + ./internal/next/\ + ./internal/rdns/\ + ./internal/schedule/\ + ./internal/stats/\ + ./internal/tools/\ + ./internal/version/\ + ./internal/whois/\ ; run_linter errcheck ./... diff --git a/scripts/make/go-tools.sh b/scripts/make/go-tools.sh index f9ccb42f..fdb70f86 100644 --- a/scripts/make/go-tools.sh +++ b/scripts/make/go-tools.sh @@ -3,7 +3,7 @@ # This comment is used to simplify checking local copies of the script. Bump # this number every time a significant change is made to this script. # -# AdGuard-Project-Version: 2 +# AdGuard-Project-Version: 3 verbose="${VERBOSE:-0}" readonly verbose diff --git a/scripts/make/helper.sh b/scripts/make/helper.sh index 6d7fe778..9c4ff88b 100644 --- a/scripts/make/helper.sh +++ b/scripts/make/helper.sh @@ -8,7 +8,7 @@ # This comment is used to simplify checking local copies of the script. Bump # this number every time a remarkable change is made to this script. # -# AdGuard-Project-Version: 2 +# AdGuard-Project-Version: 3 @@ -47,7 +47,7 @@ trap not_found EXIT run_linter() ( set +e - if [ "$VERBOSE" -lt '2' ] + if [ "${VERBOSE:-0}" -lt '2' ] then set +x fi diff --git a/scripts/make/txt-lint.sh b/scripts/make/txt-lint.sh index 06273185..578297ec 100644 --- a/scripts/make/txt-lint.sh +++ b/scripts/make/txt-lint.sh @@ -3,7 +3,7 @@ # This comment is used to simplify checking local copies of the script. Bump # this number every time a remarkable change is made to this script. # -# AdGuard-Project-Version: 3 +# AdGuard-Project-Version: 5 verbose="${VERBOSE:-0}" readonly verbose @@ -31,7 +31,7 @@ set -f -u # trailing_newlines is a simple check that makes sure that all plain-text files # have a trailing newlines to make sure that all tools work correctly with them. -trailing_newlines() { +trailing_newlines() ( nl="$( printf "\n" )" readonly nl @@ -42,15 +42,39 @@ trailing_newlines() { ':!*.zip'\ | while read -r f do - if [ "$( tail -c -1 "$f" )" != "$nl" ] + final_byte="$( tail -c -1 "$f" )" + if [ "$final_byte" != "$nl" ] then printf '%s: must have a trailing newline\n' "$f" fi done +) + +# trailing_whitespace is a simple check that makes sure that there are no +# trailing whitespace in plain-text files. +trailing_whitespace() { + # NOTE: Adjust for your project. + git ls-files\ + ':!*.bmp'\ + ':!*.jpg'\ + ':!*.mmdb'\ + ':!*.png'\ + ':!*.svg'\ + ':!*.tar.gz'\ + ':!*.webp'\ + ':!*.zip'\ + | while read -r f + do + grep -e '[[:space:]]$' -n -- "$f"\ + | sed -e "s:^:${f}\::" -e 's/ \+$/>>>&<<