From 4623817894833acfafcf778c06c8ad400f8ee5b6 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Tue, 6 Nov 2018 12:22:44 +0300 Subject: [PATCH 01/45] Add dhcp methods to the openapi.yaml --- openapi.yaml | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/openapi.yaml b/openapi.yaml index 25ba133c..8aacc03d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -24,13 +24,16 @@ tags: - name: safesearch description: 'Enforce family-friendly results in search engines' + - + name: dhcp + description: 'Build-in DHCP server management' paths: /status: get: tags: - global operationId: status - summary: 'Get DNS server status' + summary: 'Get DNS server status and settings' responses: 200: description: OK @@ -347,6 +350,59 @@ paths: responses: 200: description: OK + /dhcp/status: + post: + tags: + -dhcp + operationId: dhcpStatus + summary: "Gets the current DHCP settings" + responses: + 200: + description: OK + examples: + application/json: + dhcpEnabled: false + gatewayIp: 192.168.1.1 + subnetMask: 255.255.255.0 + rangeStart: 192.168.1.2 + rangeEnd: 192.168.10.50 + leaseDuration: 12h + - leases: + mac: 001109b3b3b8 + ip: 192.168.1.22 + hostname: dell + expires: '2018-10-30T12:18:57.223101822+03:00' + /dhcp/set_config: + post: + tags: + -dhcp + operationId: dhcpSetConfig + summary: "Updates the current DHCP server configuration" + consumes: + - application/json + parameters: + - in: body + name: dhcpConfig + description: 'Updates the DHCP module configuration' + schema: + type: json + example: '{ gatewayIp: "192.168.1.1", subnetMask: "255.255.255.0", "rangeStart": "192.168.1.2", "rangeEnd": "192.168.10.50", leaseDuration: "12h" }' + responses: + 200: + description: OK + /dhcp/check_active_dhcp: + post: + tags: + -dhcp + operationId: checkActiveDhcp + summary: "Checks if there's an active DHCP server on the network" + responses: + 200: + description: OK + examples: + application/json: + found: true + gatewayIp: 192.168.1.1 /filtering/enable: post: tags: From 09702c724eef266d582fb139d813bef46a46d44a Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Sun, 25 Nov 2018 20:09:52 +0300 Subject: [PATCH 02/45] Added swagger UI scripts --- openapi/.gitignore | 2 + openapi/README.md | 13 + openapi/index.html | 60 +++++ openapi/index.js | 8 + openapi.yaml => openapi/openapi.yaml | 57 ++--- openapi/package.json | 12 + openapi/yarn.lock | 349 +++++++++++++++++++++++++++ 7 files changed, 474 insertions(+), 27 deletions(-) create mode 100644 openapi/.gitignore create mode 100644 openapi/README.md create mode 100644 openapi/index.html create mode 100644 openapi/index.js rename openapi.yaml => openapi/openapi.yaml (94%) create mode 100644 openapi/package.json create mode 100644 openapi/yarn.lock diff --git a/openapi/.gitignore b/openapi/.gitignore new file mode 100644 index 00000000..91dfed8d --- /dev/null +++ b/openapi/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +node_modules \ No newline at end of file diff --git a/openapi/README.md b/openapi/README.md new file mode 100644 index 00000000..41d6cbee --- /dev/null +++ b/openapi/README.md @@ -0,0 +1,13 @@ +## AdGuard Home OpenAPI + +We are using [OpenAPI specification](https://swagger.io/docs/specification/about/) to generate AdGuard Home API specification. + +### How to edit the API spec + +The easiest way would be to use [Swagger Editor](http://editor.swagger.io/) and just copy/paste the YAML file there. + +### How to read the API doc + +1. `yarn install` +2. `yarn start` +3. Open `http://localhost:3000/` \ No newline at end of file diff --git a/openapi/index.html b/openapi/index.html new file mode 100644 index 00000000..e02e40e5 --- /dev/null +++ b/openapi/index.html @@ -0,0 +1,60 @@ + + + + + + AdGuard Home API + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/openapi/index.js b/openapi/index.js new file mode 100644 index 00000000..76b7b33b --- /dev/null +++ b/openapi/index.js @@ -0,0 +1,8 @@ +const express = require('express') + +const app = express() + +app.use(express.static(__dirname)) + +console.log('Open http://localhost:3000/ to examine the API spec') +app.listen(3000) diff --git a/openapi.yaml b/openapi/openapi.yaml similarity index 94% rename from openapi.yaml rename to openapi/openapi.yaml index 8aacc03d..0d7a3870 100644 --- a/openapi.yaml +++ b/openapi/openapi.yaml @@ -1,8 +1,8 @@ swagger: '2.0' info: title: 'AdGuard Home' - description: 'Control AdGuard Home server with this API' - version: 0.0.0 + description: 'AdGuard Home REST API. Admin web interface is built on top of this REST API.' + version: 0.91.0 basePath: /control schemes: - http @@ -11,29 +11,32 @@ produces: tags: - name: global - description: 'DNS server controls' + description: 'DNS server general settings and controls' + - + name: i18n + description: 'Application localization' - name: filtering description: 'Rule-based filtering' - name: safebrowsing - description: 'Malware/hazardous sites' + description: 'Blocking malware/phishing sites' - name: parental - description: 'Sites inappropriate for children' + description: 'Blocking adult and explicit materials' - name: safesearch description: 'Enforce family-friendly results in search engines' - name: dhcp - description: 'Build-in DHCP server management' + description: 'Built-in DHCP server controls' paths: /status: get: tags: - global operationId: status - summary: 'Get DNS server status and settings' + summary: 'Get DNS server current status and general settings' responses: 200: description: OK @@ -46,14 +49,14 @@ paths: running: true bootstrap_dns: 8.8.8.8:53 upstream_dns: - - 1.1.1.1 - - 1.0.0.1 + - tls://1.1.1.1 + - tls://1.0.0.1 version: "v0.1" language: "en" /enable_protection: post: tags: - -global + - global operationId: enableProtection summary: "Enable protection (turns on dnsfilter module in coredns)" responses: @@ -62,7 +65,7 @@ paths: /disable_protection: post: tags: - -global + - global operationId: disableProtection summary: "Disable protection (turns off filtering, sb, parental, safesearch temporarily by disabling dnsfilter module in coredns)" responses: @@ -344,7 +347,7 @@ paths: /stats_reset: post: tags: - -global + - global operationId: statsReset summary: "Reset all statistics to zeroes" responses: @@ -353,7 +356,7 @@ paths: /dhcp/status: post: tags: - -dhcp + - dhcp operationId: dhcpStatus summary: "Gets the current DHCP settings" responses: @@ -367,15 +370,15 @@ paths: rangeStart: 192.168.1.2 rangeEnd: 192.168.10.50 leaseDuration: 12h - - leases: - mac: 001109b3b3b8 - ip: 192.168.1.22 - hostname: dell - expires: '2018-10-30T12:18:57.223101822+03:00' + leases: + mac: 001109b3b3b8 + ip: 192.168.1.22 + hostname: dell + expires: '2018-10-30T12:18:57.223101822+03:00' /dhcp/set_config: post: tags: - -dhcp + - dhcp operationId: dhcpSetConfig summary: "Updates the current DHCP server configuration" consumes: @@ -393,7 +396,7 @@ paths: /dhcp/check_active_dhcp: post: tags: - -dhcp + - dhcp operationId: checkActiveDhcp summary: "Checks if there's an active DHCP server on the network" responses: @@ -534,13 +537,13 @@ paths: examples: application/json: enabled: false - - filters: - enabled: true - id: 1 - lastUpdated: "2018-10-30T12:18:57.223101822+03:00" - name: "AdGuard Simplified Domain Names filter" - rulesCount: 24896 - url: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt" + filters: + enabled: true + id: 1 + lastUpdated: "2018-10-30T12:18:57.223101822+03:00" + name: "AdGuard Simplified Domain Names filter" + rulesCount: 24896 + url: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt" rules: - '@@||yandex.ru^|' /filtering/set_rules: diff --git a/openapi/package.json b/openapi/package.json new file mode 100644 index 00000000..37032fe2 --- /dev/null +++ b/openapi/package.json @@ -0,0 +1,12 @@ +{ + "name": "adguard-home-api", + "version": "0.1.0", + "private": true, + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "express": "^4.16.4", + "swagger-ui-dist": "^3.20.1" + } +} \ No newline at end of file diff --git a/openapi/yarn.lock b/openapi/yarn.lock new file mode 100644 index 00000000..2eeacbe0 --- /dev/null +++ b/openapi/yarn.lock @@ -0,0 +1,349 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +accepts@~1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" + integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= + dependencies: + mime-types "~2.1.18" + negotiator "0.6.1" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +body-parser@1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" + on-finished "~2.3.0" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +express@^4.16.4: + version "4.16.4" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" + integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== + dependencies: + accepts "~1.3.5" + array-flatten "1.1.1" + body-parser "1.18.3" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.1" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.4" + qs "6.5.2" + range-parser "~1.2.0" + safe-buffer "5.1.2" + send "0.16.2" + serve-static "1.13.2" + setprototypeof "1.1.0" + statuses "~1.4.0" + type-is "~1.6.16" + utils-merge "1.0.1" + vary "~1.1.2" + +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.4.0" + unpipe "~1.0.0" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +iconv-lite@0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ipaddr.js@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" + integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +mime-db@~1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" + integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== + +mime-types@~2.1.18: + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== + dependencies: + mime-db "~1.37.0" + +mime@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +proxy-addr@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" + integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.8.0" + +qs@6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= + +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== + dependencies: + bytes "3.0.0" + http-errors "1.6.3" + iconv-lite "0.4.23" + unpipe "1.0.0" + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +send@0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.4.0" + +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" + integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.2" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== + +swagger-ui-dist@^3.20.1: + version "3.20.1" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.20.1.tgz#2e871faf29984bc5f253b8641ba38b244001cfa4" + integrity sha512-iqFNNmJWH24leUj/ohS5iZTHLZSPZse8c9F+WSCMi6ZJcRBgYKcT413c8BR5BEdKvU1kkIwvYy7C8DOjTRq9hQ== + +type-is@~1.6.16: + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" + integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.18" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= From 7106a8eb358f09383adb0206768552d593537142 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Sun, 25 Nov 2018 22:30:08 +0300 Subject: [PATCH 03/45] Added more definitions --- openapi/openapi.yaml | 480 +++++++++++++++++++++++++++++-------------- 1 file changed, 322 insertions(+), 158 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 0d7a3870..d8797005 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -3,7 +3,6 @@ info: title: 'AdGuard Home' description: 'AdGuard Home REST API. Admin web interface is built on top of this REST API.' version: 0.91.0 -basePath: /control schemes: - http produces: @@ -11,7 +10,10 @@ produces: tags: - name: global - description: 'DNS server general settings and controls' + description: 'AdGuard Home server general settings and controls' + - + name: stats + description: 'AdGuard Home statistics' - name: i18n description: 'Application localization' @@ -37,22 +39,13 @@ paths: - global operationId: status summary: 'Get DNS server current status and general settings' + produces: + - application/json responses: 200: description: OK - examples: - application/json: - dns_address: 127.0.0.1 - dns_port: 53 - protection_enabled: true - querylog_enabled: true - running: true - bootstrap_dns: 8.8.8.8:53 - upstream_dns: - - tls://1.1.1.1 - - tls://1.0.0.1 - version: "v0.1" - language: "en" + schema: + $ref: "#/definitions/ServerStatus" /enable_protection: post: tags: @@ -71,10 +64,64 @@ paths: responses: 200: description: OK + /set_upstream_dns: + post: + tags: + - global + operationId: setUpstreamDNS + summary: 'Set upstream DNS for coredns, empty value will reset it to default values' + consumes: + - text/plain + parameters: + - in: body + name: upstream + description: 'Upstream servers, separated by newline or space, port is optional after colon' + schema: + # TODO: use JSON + type: string + example: | + 1.1.1.1 + 1.0.0.1 + 8.8.8.8 8.8.4.4 + 192.168.1.104:53535 + responses: + 200: + description: OK + /test_upstream_dns: + post: + tags: + - global + operationId: testUpstreamDNS + summary: 'Test upstream DNS' + consumes: + - text/plain + parameters: + - in: body + name: upstream + description: 'Upstream servers, separated by newline or space, port is optional after colon' + schema: + # TODO: use JSON + type: string + example: | + 1.1.1.1 + 1.0.0.1 + 8.8.8.8 8.8.4.4 + 192.168.1.104:53535 + responses: + 200: + description: 'Status of testing each requested server, with "OK" meaning that server works, any other text means an error.' + examples: + application/json: + 1.1.1.1: OK + 1.0.0.1: OK + 8.8.8.8: OK + 8.8.4.4: OK + "192.168.1.104:53535": "Couldn't communicate with DNS server" + /querylog: get: tags: - - global + - stats operationId: queryLog summary: 'Get DNS server query log' parameters: @@ -87,6 +134,7 @@ paths: description: OK examples: application/json: + # TODO: move to definitions - answer: - ttl: 55 type: A @@ -134,7 +182,7 @@ paths: /querylog_enable: post: tags: - - global + - stats operationId: querylogEnable summary: 'Enable querylog' responses: @@ -143,129 +191,35 @@ paths: /querylog_disable: post: tags: - - global + - stats operationId: querylogDisable summary: 'Disable filtering' responses: 200: description: OK - /set_upstream_dns: - post: - tags: - - global - operationId: setUpstreamDNS - summary: 'Set upstream DNS for coredns, empty value will reset it to default values' - consumes: - - text/plain - parameters: - - in: body - name: upstream - description: 'Upstream servers, separated by newline or space, port is optional after colon' - schema: - type: string - example: | - 1.1.1.1 - 1.0.0.1 - 8.8.8.8 8.8.4.4 - 192.168.1.104:53535 - responses: - 200: - description: OK - /test_upstream_dns: - post: - tags: - - global - operationId: testUpstreamDNS - summary: 'Test upstream DNS' - consumes: - - text/plain - parameters: - - in: body - name: upstream - description: 'Upstream servers, separated by newline or space, port is optional after colon' - schema: - type: string - example: | - 1.1.1.1 - 1.0.0.1 - 8.8.8.8 8.8.4.4 - 192.168.1.104:53535 - responses: - 200: - description: 'Status of testing each requested server, with "OK" meaning that server works, any other text means an error.' - examples: - application/json: - 1.1.1.1: OK - 1.0.0.1: OK - 8.8.8.8: OK - 8.8.4.4: OK - "192.168.1.104:53535": "Couldn't communicate with DNS server" - /i18n/change_language: - post: - tags: - - i18n - operationId: changeLanguage - summary: "Change current language. Argument must be an ISO 639-1 two-letter code" - consumes: - - text/plain - parameters: - - in: body - name: language - description: "New language. It must be known to the server and must be an ISO 639-1 two-letter code" - schema: - type: string - example: en - /i18n/current_language: - get: - tags: - - i18n - operationId: currentLanguage - summary: "Get currently set language. Result is ISO 639-1 two-letter code. Empty result means default language." - responses: - 200: - description: OK - examples: - text/plain: - en + /stats_top: get: tags: - - global + - stats operationId: statusTop summary: 'Get DNS server top client, domain and blocked statistics' responses: 200: description: OK - examples: - application/json: - top_queried_domains: - example.org: 12312 - example.com: 12312 - example.net: 12312 - example.ru: 12312 - top_clients: - 127.0.0.1: 12312 - 192.168.0.1: 13211 - 192.168.0.2: 13211 - 192.168.0.3: 13211 - 192.168.0.4: 13211 - 192.168.0.5: 13211 - 192.168.0.6: 13211 - top_blocked_domains: - example.org: 12312 - example.com: 12312 - example.net: 12312 - example.ru: 12312 + schema: + $ref: "#/definitions/StatsTop" /stats: get: tags: - - global + - stats operationId: stats summary: 'Get DNS server statistics' responses: 200: description: 'Gives statistics since start of the server' examples: + # TODO: move to definitions application/json: dns_queries: 1201 blocked_filtering: 123 @@ -276,7 +230,7 @@ paths: /stats_history: get: tags: - - global + - stats operationId: stats_history summary: 'Get historical DNS server statistics' parameters: @@ -307,6 +261,7 @@ paths: 200: description: 'Gives statistics since start of the server. Example below is for 5 minutes. Values are from oldest to newest.' examples: + # TODO: move to definitions application/json: dns_queries: - 1201 @@ -347,52 +302,42 @@ paths: /stats_reset: post: tags: - - global + - stats operationId: statsReset summary: "Reset all statistics to zeroes" responses: 200: description: OK + /dhcp/status: - post: - tags: - - dhcp - operationId: dhcpStatus - summary: "Gets the current DHCP settings" - responses: - 200: - description: OK - examples: - application/json: - dhcpEnabled: false - gatewayIp: 192.168.1.1 - subnetMask: 255.255.255.0 - rangeStart: 192.168.1.2 - rangeEnd: 192.168.10.50 - leaseDuration: 12h - leases: - mac: 001109b3b3b8 - ip: 192.168.1.22 - hostname: dell - expires: '2018-10-30T12:18:57.223101822+03:00' + get: + tags: + - dhcp + operationId: dhcpStatus + summary: "Gets the current DHCP settings and status" + responses: + 200: + description: OK + schema: + $ref: "#/definitions/DhcpStatus" /dhcp/set_config: - post: - tags: - - dhcp - operationId: dhcpSetConfig - summary: "Updates the current DHCP server configuration" - consumes: - - application/json - parameters: - - in: body - name: dhcpConfig - description: 'Updates the DHCP module configuration' - schema: - type: json - example: '{ gatewayIp: "192.168.1.1", subnetMask: "255.255.255.0", "rangeStart": "192.168.1.2", "rangeEnd": "192.168.10.50", leaseDuration: "12h" }' - responses: - 200: - description: OK + post: + tags: + - dhcp + operationId: dhcpSetConfig + summary: "Updates the current DHCP server configuration" + consumes: + - application/json + parameters: + - in: "body" + name: "body" + description: "DHCP configuration JSON" + required: true + schema: + $ref: "#/definitions/DhcpConfig" + responses: + 200: + description: OK /dhcp/check_active_dhcp: post: tags: @@ -406,6 +351,7 @@ paths: application/json: found: true gatewayIp: 192.168.1.1 + /filtering/enable: post: tags: @@ -535,6 +481,7 @@ paths: 200: description: OK examples: + # TODO: move to definitions application/json: enabled: false filters: @@ -559,11 +506,13 @@ paths: name: rules description: 'All filtering rules, one line per rule' schema: + # TODO: move to definitions type: string example: '@@||yandex.ru^|' responses: 200: description: OK + /safebrowsing/enable: post: tags: @@ -594,6 +543,7 @@ paths: examples: application/json: enabled: false + /parental/enable: post: tags: @@ -646,6 +596,7 @@ paths: application/json: enabled: true sensitivity: 13 + /safesearch/enable: post: tags: @@ -676,6 +627,219 @@ paths: examples: application/json: enabled: false + + /i18n/change_language: + post: + tags: + - i18n + operationId: changeLanguage + summary: "Change current language. Argument must be an ISO 639-1 two-letter code" + consumes: + - text/plain + parameters: + - in: body + name: language + description: "New language. It must be known to the server and must be an ISO 639-1 two-letter code" + schema: + # TODO: use JSON? + type: string + example: en + responses: + 200: + description: OK + /i18n/current_language: + get: + tags: + - i18n + operationId: currentLanguage + summary: "Get currently set language. Result is ISO 639-1 two-letter code. Empty result means default language." + responses: + 200: + description: OK + examples: + text/plain: + en + definitions: rule: type: string + ServerStatus: + type: "object" + description: "AdGuard Home server status and configuration" + required: + - "dns_address" + - "dns_port" + - "protection_enabled" + - "querylog_enabled" + - "running" + - "bootstrap_dns" + - "upstream_dns" + - "version" + - "language" + properties: + dns_address: + type: "string" + example: "127.0.0.1" + dns_port: + type: "integer" + format: "int32" + example: 53 + minimum: 1 + maximum: 65535 + protection_enabled: + type: "boolean" + querylog_enabled: + type: "boolean" + running: + type: "boolean" + bootstrap_dns: + type: "string" + example: "8.8.8.8:53" + upstream_dns: + type: "array" + items: + type: "string" + example: + - "tls://1.1.1.1" + - "tls://1.0.0.1" + version: + type: "string" + example: "0.1" + language: + type: "string" + example: "en" + Stats: + type: "object" + description: "General server stats for the last 24 hours" + required: + - "dns_queries" + - "blocked_filtering" + - "replaced_safebrowsing" + - "replaced_parental" + - "replaced_safesearch" + - "avg_processing_time" + properties: + dns_queries: + type: "integer" + description: "Total number of DNS queries" + example: 123 + blocked_filtering: + type: "integer" + description: "Number of requests blocked by filtering rules" + example: 50 + replaced_safebrowsing: + type: "integer" + description: "Number of requests blocked by the safebrowsing module" + example: 5 + replaced_parental: + type: "integer" + description: "Number of blocked adult websites" + example: 15 + avg_processing_time: + type: "number" + format: "float" + description: "Average time in milliseconds on processing a DNS request" + example: 0.34 + + + # dns_queries: 1201 + # blocked_filtering: 123 + # replaced_safebrowsing: 5 + # replaced_parental: 18 + # replaced_safesearch: 94 + # avg_processing_time: 123 + + StatsTop: + type: "object" + description: "Server stats top charts" + required: + - "top_queried_domains" + - "top_clients" + - "top_blocked_domains" + properties: + top_queried_domains: + type: "array" + items: + type: "object" + example: + example.org: 12312 + example.com: 321 + example.net: 5555 + top_clients: + type: "array" + items: + type: "object" + example: + 127.0.0.1: 12312 + 192.168.0.1: 13211 + 192.168.0.3: 13211 + top_blocked_domains: + type: "array" + items: + type: "object" + example: + example.org: 12312 + example.com: 321 + example.net: 5555 + DhcpConfig: + type: "object" + description: "Built-in DHCP server configuration" + required: + - "enabled" + - "gateway_ip" + - "subnet_mask" + - "range_start" + - "range_end" + - "lease_duration" + properties: + enabled: + type: "boolean" + gateway_ip: + type: "string" + example: "192.168.1.1" + subnet_mask: + type: "string" + example: "255.255.255.0" + range_start: + type: "string" + example: "192.168.1.2" + range_end: + type: "string" + example: "192.168.10.50" + lease_duration: + type: "string" + example: "12h" + DhcpLease: + type: "object" + description: "DHCP lease information" + required: + - "mac" + - "ip" + - "hostname" + - "expires" + properties: + mac: + type: "string" + example: "001109b3b3b8" + ip: + type: "string" + example: "192.168.1.22" + hostname: + type: "string" + example: "dell" + expires: + type: "string" + format: "date-time" + example: "2017-07-21T17:32:28Z" + DhcpStatus: + type: "object" + required: + - "config" + - "leases" + properties: + config: + $ref: "#/definitions/DhcpConfig" + leases: + type: "array" + items: + $ref: "#/definitions/DhcpLease" From b91c829f4cfa7fadca730dc472a4de5b9cd457e2 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 26 Nov 2018 00:08:47 +0300 Subject: [PATCH 04/45] Added more openapi definitions --- openapi/openapi.yaml | 266 +++++++++++++++++++++++++------------------ 1 file changed, 158 insertions(+), 108 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index d8797005..034021df 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -132,53 +132,8 @@ paths: responses: 200: description: OK - examples: - application/json: - # TODO: move to definitions - - answer: - - ttl: 55 - type: A - value: 217.69.139.201 - - ttl: 55 - type: A - value: 94.100.180.200 - - ttl: 55 - type: A - value: 94.100.180.201 - - ttl: 55 - type: A - value: 217.69.139.200 - elapsedMs: '65.469556' - question: - class: IN - host: mail.ru - type: A - reason: DNSFILTER_NOTFILTERED_NOTFOUND - status: NOERROR - time: '2018-07-16T22:24:02+03:00' - - elapsedMs: '0.15716999999999998' - question: - class: IN - host: doubleclick.net - type: A - reason: DNSFILTER_FILTERED_BLACKLIST - rule: "||doubleclick.net^" - status: NXDOMAIN - time: '2018-07-16T22:24:02+03:00' - - answer: - - ttl: 299 - type: A - value: 176.103.133.78 - elapsedMs: '132.110929' - question: - class: IN - host: wmconvirus.narod.ru - type: A - reason: DNSFILTER_FILTERED_SAFEBROWSING - rule: adguard-malware-shavar - filterId: 1 - status: NOERROR - time: '2018-07-16T22:24:02+03:00' + schema: + $ref: '#/definitions/QueryLog' /querylog_enable: post: tags: @@ -217,22 +172,15 @@ paths: summary: 'Get DNS server statistics' responses: 200: - description: 'Gives statistics since start of the server' - examples: - # TODO: move to definitions - application/json: - dns_queries: 1201 - blocked_filtering: 123 - replaced_safebrowsing: 5 - replaced_parental: 18 - replaced_safesearch: 94 - avg_processing_time: 123 + description: 'Returns general statistics for the last 24 hours' + schema: + $ref: "#/definitions/Stats" /stats_history: get: tags: - stats operationId: stats_history - summary: 'Get historical DNS server statistics' + summary: 'Get historical DNS server statistics for the last 24 hours' parameters: - name: start_time @@ -259,46 +207,9 @@ paths: 501: description: 'Requested time window is outside of supported range. It will be supported later, but not now.' 200: - description: 'Gives statistics since start of the server. Example below is for 5 minutes. Values are from oldest to newest.' - examples: - # TODO: move to definitions - application/json: - dns_queries: - - 1201 - - 1201 - - 1201 - - 1201 - - 1201 - blocked_filtering: - - 123 - - 123 - - 123 - - 123 - - 123 - replaced_safebrowsing: - - 5 - - 5 - - 5 - - 5 - - 5 - replaced_parental: - - 18 - - 18 - - 18 - - 18 - - 18 - replaced_safesearch: - - 94 - - 94 - - 94 - - 94 - - 94 - avg_processing_time: - - 123 - - 123 - - 123 - - 123 - - 123 + description: 'Returns historical stats for the specified time interval.' + schema: + $ref: '#/definitions/StatsHistory' /stats_reset: post: tags: @@ -738,17 +649,8 @@ definitions: avg_processing_time: type: "number" format: "float" - description: "Average time in milliseconds on processing a DNS request" + description: "Average time in milliseconds on processing a DNS" example: 0.34 - - - # dns_queries: 1201 - # blocked_filtering: 123 - # replaced_safebrowsing: 5 - # replaced_parental: 18 - # replaced_safesearch: 94 - # avg_processing_time: 123 - StatsTop: type: "object" description: "Server stats top charts" @@ -781,6 +683,78 @@ definitions: example.org: 12312 example.com: 321 example.net: 5555 + StatsHistory: + type: "object" + description: "Historical stats of the DNS server. Example below is for 5 minutes. Values are from oldest to newest." + required: + - "dns_queries" + - "blocked_filtering" + - "replaced_safebrowsing" + - "replaced_parental" + - "replaced_safesearch" + - "avg_processing_time" + properties: + dns_queries: + type: "array" + items: + type: "integer" + example: + - 1201 + - 1501 + - 1251 + - 1231 + - 120 + blocked_filtering: + type: "array" + items: + type: "integer" + example: + - 421 + - 124 + - 5 + - 12 + - 43 + replaced_safebrowsing: + type: "array" + items: + type: "integer" + example: + - 1 + - 0 + - 5 + - 0 + - 0 + replaced_parental: + type: "array" + items: + type: "integer" + example: + - 120 + - 10 + - 5 + - 12 + - 1 + replaced_safesearch: + type: "array" + items: + type: "integer" + example: + - 1 + - 0 + - 0 + - 0 + - 5 + avg_processing_time: + type: "array" + items: + type: "number" + format: "float" + example: + - 1.25 + - 5.12 + - 4.12 + - 123.12 + - 0.12 DhcpConfig: type: "object" description: "Built-in DHCP server configuration" @@ -833,6 +807,7 @@ definitions: example: "2017-07-21T17:32:28Z" DhcpStatus: type: "object" + description: "Built-in DHCP server configuration and status" required: - "config" - "leases" @@ -843,3 +818,78 @@ definitions: type: "array" items: $ref: "#/definitions/DhcpLease" + DnsAnswer: + type: "object" + description: "DNS answer section" + properties: + ttl: + type: "integer" + example: 55 + type: + type: "string" + example: "A" + value: + type: "string" + example: "217.69.139.201" + DnsQuestion: + type: "object" + description: "DNS question section" + properties: + class: + type: "string" + example: "IN" + host: + type: "string" + example: "example.org" + type: + type: "string" + example: "A" + QueryLogItem: + type: "object" + description: "Query log item" + properties: + answer: + type: "array" + items: + $ref: "#/definitions/DnsAnswer" + client: + type: "string" + example: "192.168.0.1" + elapsedMs: + type: "string" + example: "54.023928" + question: + $ref: "#/definitions/DnsQuestion" + filterId: + type: "integer" + example: 123123 + description: "In case if there's a rule applied to this DNS request, this is ID of the filter that rule belongs to." + rule: + type: "string" + example: "||example.org^" + description: "Filtering rule applied to the request (if any)" + reason: + type: "string" + description: "DNS filter status" + enum: + - "NotFilteredNotFound" + - "NotFilteredWhiteList" + - "NotFilteredError" + - "FilteredBlackList" + - "FilteredSafeBrowsing" + - "FilteredParental" + - "FilteredInvalid" + - "FilteredSafeSearch" + status: + type: "string" + description: "DNS response status" + example: "NOERROR" + time: + type: "string" + description: "DNS request processing start time" + example: "2018-11-26T00:02:41+03:00" + QueryLog: + type: "array" + description: "Query log" + items: + $ref: "#/definitions/QueryLogItem" \ No newline at end of file From fcf7b2185ee18db2fa66b2d624ba68ae634d13d3 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 26 Nov 2018 12:48:17 +0300 Subject: [PATCH 05/45] Finished reworking openapi, added DHCP methods there --- openapi/openapi.yaml | 183 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 149 insertions(+), 34 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 034021df..e1f67f1d 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -11,6 +11,9 @@ tags: - name: global description: 'AdGuard Home server general settings and controls' + - + name: log + description: 'AdGuard Home query log' - name: stats description: 'AdGuard Home statistics' @@ -33,6 +36,16 @@ tags: name: dhcp description: 'Built-in DHCP server controls' paths: + + # API TO-DO LIST + # TODO: Use JSON where it is possible + # TODO: Use lower_case for all objects' properties + # TODO: Move to definitions what's missing from there + + # -------------------------------------------------- + # General settings and controls + # -------------------------------------------------- + /status: get: tags: @@ -46,6 +59,7 @@ paths: description: OK schema: $ref: "#/definitions/ServerStatus" + /enable_protection: post: tags: @@ -55,6 +69,7 @@ paths: responses: 200: description: OK + /disable_protection: post: tags: @@ -64,6 +79,7 @@ paths: responses: 200: description: OK + /set_upstream_dns: post: tags: @@ -87,6 +103,7 @@ paths: responses: 200: description: OK + /test_upstream_dns: post: tags: @@ -118,10 +135,14 @@ paths: 8.8.4.4: OK "192.168.1.104:53535": "Couldn't communicate with DNS server" + # -------------------------------------------------- + # Query log methods + # -------------------------------------------------- + /querylog: get: tags: - - stats + - log operationId: queryLog summary: 'Get DNS server query log' parameters: @@ -137,7 +158,7 @@ paths: /querylog_enable: post: tags: - - stats + - log operationId: querylogEnable summary: 'Enable querylog' responses: @@ -146,13 +167,17 @@ paths: /querylog_disable: post: tags: - - stats + - log operationId: querylogDisable summary: 'Disable filtering' responses: 200: description: OK + # -------------------------------------------------- + # General statistics methods + # -------------------------------------------------- + /stats_top: get: tags: @@ -164,6 +189,7 @@ paths: description: OK schema: $ref: "#/definitions/StatsTop" + /stats: get: tags: @@ -175,6 +201,7 @@ paths: description: 'Returns general statistics for the last 24 hours' schema: $ref: "#/definitions/Stats" + /stats_history: get: tags: @@ -210,6 +237,7 @@ paths: description: 'Returns historical stats for the specified time interval.' schema: $ref: '#/definitions/StatsHistory' + /stats_reset: post: tags: @@ -220,6 +248,10 @@ paths: 200: description: OK + # -------------------------------------------------- + # DHCP server methods + # -------------------------------------------------- + /dhcp/status: get: tags: @@ -231,6 +263,7 @@ paths: description: OK schema: $ref: "#/definitions/DhcpStatus" + /dhcp/set_config: post: tags: @@ -249,19 +282,34 @@ paths: responses: 200: description: OK - /dhcp/check_active_dhcp: - post: + + /dhcp/find_active_dhcp: + get: tags: - dhcp operationId: checkActiveDhcp - summary: "Checks if there's an active DHCP server on the network" + summary: "Searches for an active DHCP server on the network" responses: 200: description: OK - examples: - application/json: - found: true - gatewayIp: 192.168.1.1 + schema: + $ref: "#/definitions/DhcpSearchResult" + + # -------------------------------------------------- + # Filtering status methods + # -------------------------------------------------- + + /filtering/status: + get: + tags: + - filtering + operationId: filteringStatus + summary: 'Get status of rules-based filter' + responses: + 200: + description: OK + schema: + $ref: "#/definitions/FilteringStatus" /filtering/enable: post: @@ -272,6 +320,7 @@ paths: responses: 200: description: OK + /filtering/disable: post: tags: @@ -281,6 +330,7 @@ paths: responses: 200: description: OK + /filtering/add_url: put: tags: @@ -300,6 +350,7 @@ paths: responses: 200: description: OK + /filtering/remove_url: delete: tags: @@ -319,6 +370,7 @@ paths: responses: 200: description: OK + /filtering/enable_url: post: tags: @@ -338,6 +390,7 @@ paths: responses: 200: description: OK + /filtering/disable_url: post: tags: @@ -357,6 +410,7 @@ paths: responses: 200: description: OK + /filtering/refresh: post: tags: @@ -382,28 +436,7 @@ paths: responses: 200: description: OK with how many filters were actually updated - /filtering/status: - get: - tags: - - filtering - operationId: filteringStatus - summary: 'Get status of rules-based filter' - responses: - 200: - description: OK - examples: - # TODO: move to definitions - application/json: - enabled: false - filters: - enabled: true - id: 1 - lastUpdated: "2018-10-30T12:18:57.223101822+03:00" - name: "AdGuard Simplified Domain Names filter" - rulesCount: 24896 - url: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt" - rules: - - '@@||yandex.ru^|' + /filtering/set_rules: put: tags: @@ -424,6 +457,10 @@ paths: 200: description: OK + # -------------------------------------------------- + # Safebrowsing methods + # -------------------------------------------------- + /safebrowsing/enable: post: tags: @@ -433,6 +470,7 @@ paths: responses: 200: description: OK + /safebrowsing/disable: post: tags: @@ -442,6 +480,7 @@ paths: responses: 200: description: OK + /safebrowsing/status: get: tags: @@ -455,6 +494,10 @@ paths: application/json: enabled: false + # -------------------------------------------------- + # Parental control methods + # -------------------------------------------------- + /parental/enable: post: tags: @@ -485,6 +528,7 @@ paths: responses: 200: description: OK + /parental/disable: post: tags: @@ -494,6 +538,7 @@ paths: responses: 200: description: OK + /parental/status: get: tags: @@ -508,6 +553,10 @@ paths: enabled: true sensitivity: 13 + # -------------------------------------------------- + # Safe search methods + # -------------------------------------------------- + /safesearch/enable: post: tags: @@ -517,6 +566,7 @@ paths: responses: 200: description: OK + /safesearch/disable: post: tags: @@ -526,6 +576,7 @@ paths: responses: 200: description: OK + /safesearch/status: get: tags: @@ -539,6 +590,10 @@ paths: application/json: enabled: false + # -------------------------------------------------- + # I18N methods + # -------------------------------------------------- + /i18n/change_language: post: tags: @@ -558,6 +613,7 @@ paths: responses: 200: description: OK + /i18n/current_language: get: tags: @@ -572,8 +628,6 @@ paths: en definitions: - rule: - type: string ServerStatus: type: "object" description: "AdGuard Home server status and configuration" @@ -619,6 +673,56 @@ definitions: language: type: "string" example: "en" + Filter: + type: "object" + description: "Filter subscription info" + required: + - "enabled" + - "id" + - "lastUpdated" + - "name" + - "rulesCount" + - "url" + properties: + enabled: + type: "boolean" + id: + type: "integer" + example: 1234 + lastUpdated: + type: "string" + format: "date-time" + example: "2018-10-30T12:18:57.223101822+03:00" + name: + type: "string" + example: "AdGuard Simplified Domain Names filter" + rulesCount: + type: "integer" + example: 5912 + url: + type: "string" + example: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt" + FilteringStatus: + type: "object" + description: "Filtering settings" + required: + - "enabled" + - "filters" + - "user_rules" + properties: + enabled: + type: "boolean" + filters: + type: "array" + items: + $ref: "#/definitions/Filter" + user_rules: + type: "array" + items: + type: "string" + example: + - '||example.org^' + - '||example.com^' Stats: type: "object" description: "General server stats for the last 24 hours" @@ -818,6 +922,17 @@ definitions: type: "array" items: $ref: "#/definitions/DhcpLease" + DhcpSearchResult: + type: "object" + description: "Information about a DHCP server discovered in the current network" + required: + - "found" + properties: + found: + type: "boolean" + gateway_ip: + type: "string" + example: "192.168.1.1" DnsAnswer: type: "object" description: "DNS answer section" From fb2444791528cd59ca59e081a4676c138ac531b6 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 26 Nov 2018 13:22:14 +0300 Subject: [PATCH 06/45] Added version.json --- openapi/openapi.yaml | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index e1f67f1d..c491d7af 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -5,6 +5,7 @@ info: version: 0.91.0 schemes: - http +basePath: /control produces: - application/json tags: @@ -135,6 +136,24 @@ paths: 8.8.4.4: OK "192.168.1.104:53535": "Couldn't communicate with DNS server" + /version.json: + get: + tags: + - global + operationId: getVersionJson + summary: 'Gets information about the latest available version of AdGuard' + produces: + - 'application/json' + responses: + 200: + description: 'Version info' + schema: + $ref: "#/definitions/VersionInfo" + 500: + description: 'Cannot write answer' + 502: + description: 'Cannot retrieve the version.json file contents' + # -------------------------------------------------- # Query log methods # -------------------------------------------------- @@ -723,6 +742,43 @@ definitions: example: - '||example.org^' - '||example.com^' + VersionInfo: + type: "object" + description: "Information about the latest available version of AdGuard Home" + required: + - "version" + - "announcement" + - "announcement_url" + - "download_darwin_amd64" + - "download_linux_amd64" + - "download_linux_386" + - "download_linux_arm" + - "selfupdate_min_version" + properties: + version: + type: "string" + example: "v0.9" + announcement: + type: "string" + example: "AdGuard Home v0.9 is now available!" + announcement_url: + type: "string" + example: "https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.9" + download_darwin_amd64: + type: "string" + example: "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.9/AdGuardHome_v0.9_MacOS.zip" + download_linux_amd64: + type: "string" + example: "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.9/AdGuardHome_v0.9_linux_amd64.tar.gz" + download_linux_386: + type: "string" + example: "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.9/AdGuardHome_v0.9_linux_386.tar.gz" + download_linux_arm: + type: "string" + example: "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.9/AdGuardHome_v0.9_linux_arm.tar.gz" + selfupdate_min_version: + type: "string" + example: "v0.0" Stats: type: "object" description: "General server stats for the last 24 hours" From 390883126c2160eefe23e9df686d00cdfaebcf85 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Wed, 12 Dec 2018 17:25:42 +0300 Subject: [PATCH 07/45] Change openapi doc port from 3000 to 4000 to avoid clashing with adguardhome. --- openapi/README.md | 2 +- openapi/index.js | 4 ++-- openapi/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openapi/README.md b/openapi/README.md index 41d6cbee..cd689408 100644 --- a/openapi/README.md +++ b/openapi/README.md @@ -10,4 +10,4 @@ The easiest way would be to use [Swagger Editor](http://editor.swagger.io/) and 1. `yarn install` 2. `yarn start` -3. Open `http://localhost:3000/` \ No newline at end of file +3. Open `http://localhost:4000/` diff --git a/openapi/index.js b/openapi/index.js index 76b7b33b..a63d206c 100644 --- a/openapi/index.js +++ b/openapi/index.js @@ -4,5 +4,5 @@ const app = express() app.use(express.static(__dirname)) -console.log('Open http://localhost:3000/ to examine the API spec') -app.listen(3000) +console.log('Open http://localhost:4000/ to examine the API spec') +app.listen(4000) diff --git a/openapi/package.json b/openapi/package.json index 37032fe2..9b3d78ed 100644 --- a/openapi/package.json +++ b/openapi/package.json @@ -9,4 +9,4 @@ "express": "^4.16.4", "swagger-ui-dist": "^3.20.1" } -} \ No newline at end of file +} From dd21f497e3ab9943989ac087e11b3d297768aeb6 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Wed, 12 Dec 2018 18:12:51 +0300 Subject: [PATCH 08/45] Added initial layout for DHCP server config --- client/package-lock.json | 29 +++- client/package.json | 1 + client/src/__locales/en.json | 15 +- client/src/actions/index.js | 70 +++++++++ client/src/api/Api.js | 67 +++++++++ client/src/components/Settings/Dhcp/Form.js | 134 ++++++++++++++++++ client/src/components/Settings/Dhcp/Leases.js | 36 +++++ client/src/components/Settings/Dhcp/index.js | 93 ++++++++++++ client/src/components/Settings/Settings.css | 13 ++ client/src/components/Settings/index.js | 9 ++ client/src/containers/Settings.js | 21 ++- client/src/helpers/constants.js | 1 + client/src/reducers/index.js | 35 +++++ 13 files changed, 514 insertions(+), 10 deletions(-) create mode 100644 client/src/components/Settings/Dhcp/Form.js create mode 100644 client/src/components/Settings/Dhcp/Leases.js create mode 100644 client/src/components/Settings/Dhcp/index.js diff --git a/client/package-lock.json b/client/package-lock.json index fc6ea4f1..585d2b4c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -4126,6 +4126,11 @@ "next-tick": "1" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", @@ -6588,7 +6593,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -6638,7 +6643,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -7387,8 +7392,7 @@ "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, "is-regex": { "version": "1.0.4", @@ -13202,6 +13206,21 @@ "reduce-reducers": "^0.1.0" } }, + "redux-form": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-7.4.2.tgz", + "integrity": "sha512-QxC36s4Lelx5Cr8dbpxqvl23dwYOydeAX8c6YPmgkz/Dhj053C16S2qoyZN6LO6HJ2oUF00rKsAyE94GwOUhFA==", + "requires": { + "es6-error": "^4.1.1", + "hoist-non-react-statics": "^2.5.4", + "invariant": "^2.2.4", + "is-promise": "^2.1.0", + "lodash": "^4.17.10", + "lodash-es": "^4.17.10", + "prop-types": "^15.6.1", + "react-lifecycles-compat": "^3.0.4" + } + }, "redux-thunk": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", @@ -15003,7 +15022,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, diff --git a/client/package.json b/client/package.json index 7d200e80..6b518d60 100644 --- a/client/package.json +++ b/client/package.json @@ -31,6 +31,7 @@ "react-transition-group": "^2.4.0", "redux": "^4.0.0", "redux-actions": "^2.4.0", + "redux-form": "^7.4.2", "redux-thunk": "^2.3.0", "svg-url-loader": "^2.3.2", "whatwg-fetch": "2.0.3" diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index f6045521..d20555fb 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -127,5 +127,16 @@ "category_label": "Category", "rule_label": "Rule", "filter_label": "Filter", - "unknown_filter": "Unknown filter {{filterId}}" -} \ No newline at end of file + "unknown_filter": "Unknown filter {{filterId}}", + "refresh_status": "Refresh status", + "save_config": "Save config", + "enabled_dhcp": "DHCP server enabled", + "disabled_dhcp": "DHCP server disabled", + "dhcp_title": "DHCP server", + "dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.", + "dhcp_enable": "Enable DHCP server", + "dhcp_disable": "Disable DHCP server", + "dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.", + "dhcp_leases": "DHCP leases", + "dhcp_leases_not_found": "No DHCP leases found" +} diff --git a/client/src/actions/index.js b/client/src/actions/index.js index a4da8c9a..cd56647e 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -522,3 +522,73 @@ export const getLanguage = () => async (dispatch) => { dispatch(getLanguageFailure()); } }; + +export const getDhcpStatusRequest = createAction('GET_DHCP_STATUS_REQUEST'); +export const getDhcpStatusSuccess = createAction('GET_DHCP_STATUS_SUCCESS'); +export const getDhcpStatusFailure = createAction('GET_DHCP_STATUS_FAILURE'); + +export const getDhcpStatus = () => async (dispatch) => { + dispatch(getDhcpStatusRequest()); + try { + const status = await apiClient.getDhcpStatus(); + dispatch(getDhcpStatusSuccess(status)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getDhcpStatusFailure()); + } +}; + +export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST'); +export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS'); +export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE'); + +export const setDhcpConfig = config => async (dispatch) => { + dispatch(setDhcpConfigRequest()); + try { + await apiClient.setDhcpConfig(config); + dispatch(setDhcpConfigSuccess()); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(setDhcpConfigFailure()); + } +}; + +export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST'); +export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS'); +export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE'); + +export const findActiveDhcp = () => async (dispatch) => { + dispatch(findActiveDhcpRequest()); + try { + const result = await apiClient.findActiveDhcp(); + dispatch(findActiveDhcpSuccess(result)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(findActiveDhcpFailure()); + } +}; + +export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST'); +export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE'); +export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS'); + +export const toggleDhcp = status => async (dispatch) => { + dispatch(toggleDhcpRequest()); + let successMessage = ''; + + try { + if (status) { + successMessage = 'disabled_dhcp'; + await apiClient.disableGlobalProtection(); + } else { + successMessage = 'enabled_dhcp'; + await apiClient.enableGlobalProtection(); + } + + dispatch(addSuccessToast(successMessage)); + dispatch(toggleDhcpSuccess()); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(toggleDhcpFailure()); + } +}; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index f0d90941..966b5199 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -302,4 +302,71 @@ export default class Api { }; return this.makeRequest(path, method, parameters); } + + // DHCP + DHCP_STATUS = { path: 'dhcp/status', method: 'GET' }; + DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' }; + DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'GET' }; + + getDhcpStatus() { + // const { path, method } = this.DHCP_STATUS; + // return this.makeRequest(path, method); + + const example = { + config: { + enabled: false, + gateway_ip: '192.168.1.1', + subnet_mask: '255.255.255.0', + range_start: '192.168.1.2', + range_end: '192.168.10.50', + lease_duration: '43200', + }, + leases: [ + { + mac: '001109b3b3b8', + ip: '192.168.1.22', + hostname: 'dell', + expires: '2017-07-21T17:32:28Z', + }, + { + mac: '001109b3b3b9', + ip: '192.168.1.23', + hostname: 'dell', + expires: '2017-07-21T17:32:28Z', + }, + ], + }; + + return new Promise((resolve) => { + setTimeout(() => { + resolve(example); + }, 1000); + }); + } + + setDhcpConfig(config) { + // const { path, method } = this.DHCP_SET_CONFIG; + // const parameters = config; + // return this.makeRequest(path, method, parameters); + + return new Promise((resolve) => { + setTimeout(() => { + resolve(window.alert(`Set config:\n\n${JSON.stringify(config, null, 2)}`)); + }, 1000); + }); + } + + findActiveDhcp() { + // const { path, method } = this.DHCP_FIND_ACTIVE; + // return this.makeRequest(path, method); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + gateway_ip: '127.0.0.1', + found: true, + }); + }, 10000); + }); + } } diff --git a/client/src/components/Settings/Dhcp/Form.js b/client/src/components/Settings/Dhcp/Form.js new file mode 100644 index 00000000..a4caea0b --- /dev/null +++ b/client/src/components/Settings/Dhcp/Form.js @@ -0,0 +1,134 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { Field, reduxForm } from 'redux-form'; +import { R_IPV4 } from '../../../helpers/constants'; + +const required = (value) => { + if (value) { + return false; + } + return 'Required field'; +}; + +const ipv4 = (value) => { + if (value && !new RegExp(R_IPV4).test(value)) { + return 'Invalid IPv4 format'; + } + return false; +}; + +const renderField = ({ + input, className, placeholder, type, disabled, meta: { touched, error }, +}) => ( + + + {!disabled && touched && (error && {error})} + +); + +const Form = (props) => { + const { + handleSubmit, pristine, submitting, enabled, + } = props; + + return ( +
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+
+
+ + +
+ ); +}; + +Form.propTypes = { + handleSubmit: PropTypes.func, + pristine: PropTypes.bool, + submitting: PropTypes.bool, + enabled: PropTypes.bool, +}; + +export default reduxForm({ + form: 'dhcpForm', +})(Form); diff --git a/client/src/components/Settings/Dhcp/Leases.js b/client/src/components/Settings/Dhcp/Leases.js new file mode 100644 index 00000000..89959946 --- /dev/null +++ b/client/src/components/Settings/Dhcp/Leases.js @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ReactTable from 'react-table'; +import { withNamespaces } from 'react-i18next'; + +const columns = [{ + Header: 'MAC', + accessor: 'mac', +}, { + Header: 'IP', + accessor: 'ip', +}, { + Header: 'Hostname', + accessor: 'hostname', +}, { + Header: 'Expires', + accessor: 'expires', +}]; + +const Leases = props => ( + +); + +Leases.propTypes = { + leases: PropTypes.array, + t: PropTypes.func, +}; + +export default withNamespaces()(Leases); diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js new file mode 100644 index 00000000..2b584bfb --- /dev/null +++ b/client/src/components/Settings/Dhcp/index.js @@ -0,0 +1,93 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { Trans, withNamespaces } from 'react-i18next'; + +import Form from './Form'; +import Leases from './Leases'; +import Card from '../../ui/Card'; + +class Dhcp extends Component { + handleFormSubmit = (values) => { + this.props.setDhcpConfig(values); + }; + + handleRefresh = () => { + this.props.findActiveDhcp(); + } + + getToggleDhcpButton = () => { + const { enabled } = this.props.dhcp.config; + const buttonText = enabled ? 'dhcp_disable' : 'dhcp_enable'; + const buttonClass = enabled ? 'btn-gray' : 'btn-success'; + + return ( + + ); + } + + render() { + const { t, dhcp } = this.props; + const statusButtonClass = classnames({ + 'btn btn-primary btn-standart': true, + 'btn btn-primary btn-standart btn-loading': dhcp.processingStatus, + }); + + return ( + + {!dhcp.processing && + +
+
+
+ {this.getToggleDhcpButton()} + +
+ {dhcp.active && !dhcp.active.found && +
+ dhcp_not_found +
+ } +
+
+
+
+
+ } + {!dhcp.processing && dhcp.config.enabled && + +
+
+ +
+
+
+ } +
+ ); + } +} + +Dhcp.propTypes = { + dhcp: PropTypes.object, + toggleDhcp: PropTypes.func, + getDhcpStatus: PropTypes.func, + setDhcpConfig: PropTypes.func, + findActiveDhcp: PropTypes.func, + handleSubmit: PropTypes.func, + t: PropTypes.func, +}; + +export default withNamespaces()(Dhcp); diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css index 380a70f6..f9b50007 100644 --- a/client/src/components/Settings/Settings.css +++ b/client/src/components/Settings/Settings.css @@ -1,4 +1,5 @@ .form__group { + position: relative; margin-bottom: 15px; } @@ -6,6 +7,10 @@ margin-bottom: 0; } +.form__group--dhcp:last-child { + margin-bottom: 15px; +} + .btn-standart { padding-left: 20px; padding-right: 20px; @@ -18,3 +23,11 @@ .form-control--textarea-large { min-height: 240px; } + +.form__message { + font-size: 11px; +} + +.form__message--error { + color: #cd201f; +} diff --git a/client/src/components/Settings/index.js b/client/src/components/Settings/index.js index 74a02a20..aafc64e2 100644 --- a/client/src/components/Settings/index.js +++ b/client/src/components/Settings/index.js @@ -2,6 +2,7 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { withNamespaces, Trans } from 'react-i18next'; import Upstream from './Upstream'; +import Dhcp from './Dhcp'; import Checkbox from '../ui/Checkbox'; import Loading from '../ui/Loading'; import PageTitle from '../ui/PageTitle'; @@ -34,6 +35,7 @@ class Settings extends Component { componentDidMount() { this.props.initSettings(this.settings); + this.props.getDhcpStatus(); } handleUpstreamChange = (value) => { @@ -92,6 +94,13 @@ class Settings extends Component { handleUpstreamSubmit={this.handleUpstreamSubmit} handleUpstreamTest={this.handleUpstreamTest} /> + diff --git a/client/src/containers/Settings.js b/client/src/containers/Settings.js index 144e968a..062fc2f2 100644 --- a/client/src/containers/Settings.js +++ b/client/src/containers/Settings.js @@ -1,10 +1,21 @@ import { connect } from 'react-redux'; -import { initSettings, toggleSetting, handleUpstreamChange, setUpstream, testUpstream, addErrorToast } from '../actions'; +import { + initSettings, + toggleSetting, + handleUpstreamChange, + setUpstream, + testUpstream, + addErrorToast, + toggleDhcp, + getDhcpStatus, + setDhcpConfig, + findActiveDhcp, +} from '../actions'; import Settings from '../components/Settings'; const mapStateToProps = (state) => { - const { settings, dashboard } = state; - const props = { settings, dashboard }; + const { settings, dashboard, dhcp } = state; + const props = { settings, dashboard, dhcp }; return props; }; @@ -15,6 +26,10 @@ const mapDispatchToProps = { setUpstream, testUpstream, addErrorToast, + toggleDhcp, + getDhcpStatus, + setDhcpConfig, + findActiveDhcp, }; export default connect( diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 1fe9348a..f0b2aea7 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -1,4 +1,5 @@ export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/\w[\w_\-.]*\.[a-z]{2,8}[^\s]*$/; +export const R_IPV4 = /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/g; export const STATS_NAMES = { avg_processing_time: 'average_processing_time', diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 9a54f249..0702a06a 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -2,6 +2,7 @@ import { combineReducers } from 'redux'; import { handleActions } from 'redux-actions'; import { loadingBarReducer } from 'react-redux-loading-bar'; import nanoid from 'nanoid'; +import { reducer as formReducer } from 'redux-form'; import versionCompare from '../helpers/versionCompare'; import * as actions from '../actions'; @@ -35,6 +36,7 @@ const settings = handleActions({ processing: true, processingTestUpstream: false, processingSetUpstream: false, + processingDhcpStatus: false, }); const dashboard = handleActions({ @@ -258,11 +260,44 @@ const toasts = handleActions({ }, }, { notices: [] }); +const dhcp = handleActions({ + [actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }), + [actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }), + [actions.getDhcpStatusSuccess]: (state, { payload }) => { + const newState = { + ...state, + ...payload, + processing: false, + }; + return newState; + }, + + [actions.findActiveDhcpRequest]: state => ({ ...state, processingStatus: true }), + [actions.findActiveDhcpFailure]: state => ({ ...state, processingStatus: false }), + [actions.findActiveDhcpSuccess]: (state, { payload }) => ({ + ...state, + active: payload, + processingStatus: false, + }), + + [actions.toggleDhcpSuccess]: (state) => { + const { config } = state; + const newConfig = { ...config, enabled: !config.enabled }; + const newState = { ...state, config: newConfig }; + return newState; + }, +}, { + processing: true, + processingStatus: false, +}); + export default combineReducers({ settings, dashboard, queryLogs, filtering, toasts, + dhcp, loadingBar: loadingBarReducer, + form: formReducer, }); From 9294c9ecb2db05dab83b3e168df80c27be15f000 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Wed, 12 Dec 2018 20:22:45 +0300 Subject: [PATCH 09/45] Add DHCP API stubs for JS development. --- config.go | 29 +++++++++++++++++++++++++++++ control.go | 3 +++ dhcp.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 dhcp.go diff --git a/config.go b/config.go index 1f7464b2..8566de68 100644 --- a/config.go +++ b/config.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "sync" + "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" "github.com/AdguardTeam/AdGuardHome/dnsforward" @@ -31,6 +32,7 @@ type configuration struct { DNS dnsConfig `yaml:"dns"` Filters []filter `yaml:"filters"` UserRules []string `yaml:"user_rules"` + DHCP dhcpState `yaml:"dhcp"` sync.RWMutex `yaml:"-"` @@ -48,6 +50,30 @@ type dnsConfig struct { var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"} +// field ordering is important -- yaml fields will mirror ordering from here +type dhcpState struct { + dhcpConfig + Leases []dhcpLease +} + +// field ordering is important -- yaml fields will mirror ordering from here +type dhcpConfig struct { + Enabled bool + GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"` + SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"` + RangeStart string `json:"range_start" yaml:"range_start"` + RangeEnd string `json:"range_end" yaml:"range_end"` + LeaseDuration time.Duration `json:"lease_duration" yaml:"lease_duration"` +} + +// field ordering is important -- yaml fields will mirror ordering from here +type dhcpLease struct { + HWAddr [6]byte `json:"mac" yaml:"hwaddr"` + IP string `json:"ip"` // json by default keeps IP uppercase but we need lowercase + Hostname string + Expires time.Time +} + // initialize to default values, will be changed later when reading config or parsing command line var config = configuration{ ourConfigFilename: "AdGuardHome.yaml", @@ -72,6 +98,9 @@ var config = configuration{ {Filter: dnsfilter.Filter{ID: 3}, Enabled: false, URL: "https://hosts-file.net/ad_servers.txt", Name: "hpHosts - Ad and Tracking servers only"}, {Filter: dnsfilter.Filter{ID: 4}, Enabled: false, URL: "http://www.malwaredomainlist.com/hostslist/hosts.txt", Name: "MalwareDomainList.com Hosts List"}, }, + DHCP: dhcpState{dhcpConfig: dhcpConfig{ + LeaseDuration: time.Hour * 12, + }}, SchemaVersion: currentSchemaVersion, } diff --git a/control.go b/control.go index 78235a48..0b00f08c 100644 --- a/control.go +++ b/control.go @@ -719,4 +719,7 @@ func registerControlHandlers() { http.HandleFunc("/control/safesearch/enable", optionalAuth(ensurePOST(handleSafeSearchEnable))) http.HandleFunc("/control/safesearch/disable", optionalAuth(ensurePOST(handleSafeSearchDisable))) http.HandleFunc("/control/safesearch/status", optionalAuth(ensureGET(handleSafeSearchStatus))) + http.HandleFunc("/control/dhcp/status", optionalAuth(ensureGET(handleDHCPStatus))) + http.HandleFunc("/control/dhcp/set_config", optionalAuth(ensurePOST(handleDHCPSetConfig))) + http.HandleFunc("/control/dhcp/find_active_dhcp", optionalAuth(ensurePOST(handleDHCPFindActiveServer))) } diff --git a/dhcp.go b/dhcp.go new file mode 100644 index 00000000..5a6fc490 --- /dev/null +++ b/dhcp.go @@ -0,0 +1,45 @@ +package main + +import ( + "encoding/json" + "math/rand" + "net/http" +) + +func handleDHCPStatus(w http.ResponseWriter, r *http.Request) { + status := map[string]interface{}{ + "config": config.DHCP.dhcpConfig, + "leases": config.DHCP.Leases, + } + + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(status) + if err != nil { + httpError(w, http.StatusInternalServerError, "Unable to marshal DHCP status json: %s", err) + return + } +} + +func handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { + newconfig := dhcpConfig{} + err := json.NewDecoder(r.Body).Decode(&newconfig) + if err != nil { + httpError(w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err) + return + } + + config.DHCP.dhcpConfig = newconfig +} + +// TODO: implement +func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { + found := map[string]bool{ + "found": rand.Intn(2) == 1, + } + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(found) + if err != nil { + httpError(w, http.StatusBadRequest, "Failed to marshal DHCP found json: %s", err) + return + } +} From 96fbf7f13448c18e4b78322bd467e8dfbf2e9826 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Thu, 13 Dec 2018 13:17:41 +0300 Subject: [PATCH 10/45] Fix yaml marshalling panic. --- config.go | 4 ++-- dhcp.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config.go b/config.go index 8566de68..6f01d023 100644 --- a/config.go +++ b/config.go @@ -52,7 +52,7 @@ var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"} // field ordering is important -- yaml fields will mirror ordering from here type dhcpState struct { - dhcpConfig + Config dhcpConfig Leases []dhcpLease } @@ -98,7 +98,7 @@ var config = configuration{ {Filter: dnsfilter.Filter{ID: 3}, Enabled: false, URL: "https://hosts-file.net/ad_servers.txt", Name: "hpHosts - Ad and Tracking servers only"}, {Filter: dnsfilter.Filter{ID: 4}, Enabled: false, URL: "http://www.malwaredomainlist.com/hostslist/hosts.txt", Name: "MalwareDomainList.com Hosts List"}, }, - DHCP: dhcpState{dhcpConfig: dhcpConfig{ + DHCP: dhcpState{Config: dhcpConfig{ LeaseDuration: time.Hour * 12, }}, SchemaVersion: currentSchemaVersion, diff --git a/dhcp.go b/dhcp.go index 5a6fc490..2cbc1cb6 100644 --- a/dhcp.go +++ b/dhcp.go @@ -8,7 +8,7 @@ import ( func handleDHCPStatus(w http.ResponseWriter, r *http.Request) { status := map[string]interface{}{ - "config": config.DHCP.dhcpConfig, + "config": config.DHCP.Config, "leases": config.DHCP.Leases, } @@ -28,7 +28,7 @@ func handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { return } - config.DHCP.dhcpConfig = newconfig + config.DHCP.Config = newconfig } // TODO: implement From d46b65f982e1f9c51ee407a6fb31f32956540123 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Thu, 13 Dec 2018 14:38:00 +0300 Subject: [PATCH 11/45] Add enable/disable for DHCP server --- client/src/__locales/en.json | 3 +- client/src/actions/index.js | 9 +-- client/src/api/Api.js | 63 +++---------------- client/src/components/Settings/Dhcp/Leases.js | 2 +- client/src/components/Settings/Dhcp/index.js | 8 +-- client/src/reducers/index.js | 4 ++ config.go | 2 +- 7 files changed, 27 insertions(+), 64 deletions(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index d20555fb..0148b7ea 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -138,5 +138,6 @@ "dhcp_disable": "Disable DHCP server", "dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.", "dhcp_leases": "DHCP leases", - "dhcp_leases_not_found": "No DHCP leases found" + "dhcp_leases_not_found": "No DHCP leases found", + "dhcp_config_saved": "Saved DHCP server config" } diff --git a/client/src/actions/index.js b/client/src/actions/index.js index cd56647e..e23ccb81 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -546,6 +546,7 @@ export const setDhcpConfig = config => async (dispatch) => { dispatch(setDhcpConfigRequest()); try { await apiClient.setDhcpConfig(config); + dispatch(addSuccessToast('dhcp_config_saved')); dispatch(setDhcpConfigSuccess()); } catch (error) { dispatch(addErrorToast({ error })); @@ -572,17 +573,17 @@ export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST'); export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE'); export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS'); -export const toggleDhcp = status => async (dispatch) => { +export const toggleDhcp = config => async (dispatch) => { dispatch(toggleDhcpRequest()); let successMessage = ''; try { - if (status) { + if (config.enabled) { successMessage = 'disabled_dhcp'; - await apiClient.disableGlobalProtection(); + await apiClient.setDhcpConfig({ ...config, enabled: false }); } else { successMessage = 'enabled_dhcp'; - await apiClient.enableGlobalProtection(); + await apiClient.setDhcpConfig({ ...config, enabled: true }); } dispatch(addSuccessToast(successMessage)); diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 966b5199..f7b363e9 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -309,64 +309,21 @@ export default class Api { DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'GET' }; getDhcpStatus() { - // const { path, method } = this.DHCP_STATUS; - // return this.makeRequest(path, method); - - const example = { - config: { - enabled: false, - gateway_ip: '192.168.1.1', - subnet_mask: '255.255.255.0', - range_start: '192.168.1.2', - range_end: '192.168.10.50', - lease_duration: '43200', - }, - leases: [ - { - mac: '001109b3b3b8', - ip: '192.168.1.22', - hostname: 'dell', - expires: '2017-07-21T17:32:28Z', - }, - { - mac: '001109b3b3b9', - ip: '192.168.1.23', - hostname: 'dell', - expires: '2017-07-21T17:32:28Z', - }, - ], - }; - - return new Promise((resolve) => { - setTimeout(() => { - resolve(example); - }, 1000); - }); + const { path, method } = this.DHCP_STATUS; + return this.makeRequest(path, method); } setDhcpConfig(config) { - // const { path, method } = this.DHCP_SET_CONFIG; - // const parameters = config; - // return this.makeRequest(path, method, parameters); - - return new Promise((resolve) => { - setTimeout(() => { - resolve(window.alert(`Set config:\n\n${JSON.stringify(config, null, 2)}`)); - }, 1000); - }); + const { path, method } = this.DHCP_SET_CONFIG; + const parameters = { + data: config, + headers: { 'Content-Type': 'application/json' }, + }; + return this.makeRequest(path, method, parameters); } findActiveDhcp() { - // const { path, method } = this.DHCP_FIND_ACTIVE; - // return this.makeRequest(path, method); - - return new Promise((resolve) => { - setTimeout(() => { - resolve({ - gateway_ip: '127.0.0.1', - found: true, - }); - }, 10000); - }); + const { path, method } = this.DHCP_FIND_ACTIVE; + return this.makeRequest(path, method); } } diff --git a/client/src/components/Settings/Dhcp/Leases.js b/client/src/components/Settings/Dhcp/Leases.js index 89959946..562f3243 100644 --- a/client/src/components/Settings/Dhcp/Leases.js +++ b/client/src/components/Settings/Dhcp/Leases.js @@ -19,7 +19,7 @@ const columns = [{ const Leases = props => ( { - const { enabled } = this.props.dhcp.config; - const buttonText = enabled ? 'dhcp_disable' : 'dhcp_enable'; - const buttonClass = enabled ? 'btn-gray' : 'btn-success'; + const { config } = this.props.dhcp; + const buttonText = config.enabled ? 'dhcp_disable' : 'dhcp_enable'; + const buttonClass = config.enabled ? 'btn-gray' : 'btn-success'; return ( - ); diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 0702a06a..6f0f61d4 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -289,6 +289,10 @@ const dhcp = handleActions({ }, { processing: true, processingStatus: false, + config: { + enabled: false, + }, + leases: [], }); export default combineReducers({ diff --git a/config.go b/config.go index 6f01d023..9cd54ddf 100644 --- a/config.go +++ b/config.go @@ -58,7 +58,7 @@ type dhcpState struct { // field ordering is important -- yaml fields will mirror ordering from here type dhcpConfig struct { - Enabled bool + Enabled bool `json:"enabled" yaml:"enabled"` GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"` SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"` RangeStart string `json:"range_start" yaml:"range_start"` From a74c2248fb34f0914a3f0c9f6046b9ea362e25f1 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Thu, 13 Dec 2018 14:39:59 +0300 Subject: [PATCH 12/45] Send dhcp/find_active_dhcp as POST request --- client/src/actions/index.js | 4 ++-- client/src/api/Api.js | 2 +- openapi/openapi.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/actions/index.js b/client/src/actions/index.js index e23ccb81..7e07c6a0 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -561,8 +561,8 @@ export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE'); export const findActiveDhcp = () => async (dispatch) => { dispatch(findActiveDhcpRequest()); try { - const result = await apiClient.findActiveDhcp(); - dispatch(findActiveDhcpSuccess(result)); + await apiClient.findActiveDhcp(); + dispatch(findActiveDhcpSuccess()); } catch (error) { dispatch(addErrorToast({ error })); dispatch(findActiveDhcpFailure()); diff --git a/client/src/api/Api.js b/client/src/api/Api.js index f7b363e9..4aac23f5 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -306,7 +306,7 @@ export default class Api { // DHCP DHCP_STATUS = { path: 'dhcp/status', method: 'GET' }; DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' }; - DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'GET' }; + DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' }; getDhcpStatus() { const { path, method } = this.DHCP_STATUS; diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index c491d7af..97b94c99 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -303,7 +303,7 @@ paths: description: OK /dhcp/find_active_dhcp: - get: + post: tags: - dhcp operationId: checkActiveDhcp From 59adad4d53442df32ab4972c51b6d1fd827a2959 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Thu, 13 Dec 2018 14:51:35 +0300 Subject: [PATCH 13/45] DHCP -- Use uint64 for lease duration --- config.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index 9cd54ddf..b3d88388 100644 --- a/config.go +++ b/config.go @@ -58,12 +58,12 @@ type dhcpState struct { // field ordering is important -- yaml fields will mirror ordering from here type dhcpConfig struct { - Enabled bool `json:"enabled" yaml:"enabled"` - GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"` - SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"` - RangeStart string `json:"range_start" yaml:"range_start"` - RangeEnd string `json:"range_end" yaml:"range_end"` - LeaseDuration time.Duration `json:"lease_duration" yaml:"lease_duration"` + Enabled bool `json:"enabled" yaml:"enabled"` + GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"` + SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"` + RangeStart string `json:"range_start" yaml:"range_start"` + RangeEnd string `json:"range_end" yaml:"range_end"` + LeaseDuration uint64 `json:"lease_duration" yaml:"lease_duration"` // in seconds } // field ordering is important -- yaml fields will mirror ordering from here From 39bc55e43012dafca1ff72bad707da2df6fcc56e Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Thu, 13 Dec 2018 14:53:25 +0300 Subject: [PATCH 14/45] Fixup of previous commit. --- config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.go b/config.go index b3d88388..4133aecb 100644 --- a/config.go +++ b/config.go @@ -99,7 +99,7 @@ var config = configuration{ {Filter: dnsfilter.Filter{ID: 4}, Enabled: false, URL: "http://www.malwaredomainlist.com/hostslist/hosts.txt", Name: "MalwareDomainList.com Hosts List"}, }, DHCP: dhcpState{Config: dhcpConfig{ - LeaseDuration: time.Hour * 12, + LeaseDuration: 12 * 60 * 60, // in seconds }}, SchemaVersion: currentSchemaVersion, } From 52b81a27fb54e8db4e13fb3e4e5f4cb3cd7048cf Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Thu, 13 Dec 2018 15:26:47 +0300 Subject: [PATCH 15/45] Send lease duration as number --- client/src/components/Settings/Dhcp/Form.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/client/src/components/Settings/Dhcp/Form.js b/client/src/components/Settings/Dhcp/Form.js index a4caea0b..e65ab04a 100644 --- a/client/src/components/Settings/Dhcp/Form.js +++ b/client/src/components/Settings/Dhcp/Form.js @@ -4,7 +4,7 @@ import { Field, reduxForm } from 'redux-form'; import { R_IPV4 } from '../../../helpers/constants'; const required = (value) => { - if (value) { + if (value || value === 0) { return false; } return 'Required field'; @@ -17,6 +17,15 @@ const ipv4 = (value) => { return false; }; +const isPositive = (value) => { + if ((value || value === 0) && (value <= 0)) { + return 'Must be greater than 0'; + } + return false; +}; + +const toNumber = value => value && parseInt(value, 10); + const renderField = ({ input, className, placeholder, type, disabled, meta: { touched, error }, }) => ( @@ -104,8 +113,9 @@ const Form = (props) => { type="number" className="form-control" placeholder="Lease duration" - validate={[required]} + validate={[required, isPositive]} disabled={!enabled} + normalize={toNumber} /> From 9fa85a5c48cc5db772c3fd4c0decddd30a8a6b31 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Mon, 17 Dec 2018 14:24:54 +0300 Subject: [PATCH 16/45] Added DHCP form strings to translations --- client/src/__locales/en.json | 12 ++++++- client/src/components/Settings/Dhcp/Form.js | 39 ++++++++++++--------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 0148b7ea..0ffe12f7 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -139,5 +139,15 @@ "dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.", "dhcp_leases": "DHCP leases", "dhcp_leases_not_found": "No DHCP leases found", - "dhcp_config_saved": "Saved DHCP server config" + "dhcp_config_saved": "Saved DHCP server config", + "form_error_required": "Required field", + "form_error_ip_format": "Invalid IPv4 format", + "form_error_positive": "Must be greater than 0", + "dhcp_form_gateway_input": "Gateway IP", + "dhcp_form_subnet_input": "Subnet mask", + "dhcp_form_range_title": "Range of IP addresses", + "dhcp_form_range_start": "Range start", + "dhcp_form_range_end": "Range end", + "dhcp_form_lease_title": "DHCP lease time (in seconds)", + "dhcp_form_lease_input": "Lease duration" } diff --git a/client/src/components/Settings/Dhcp/Form.js b/client/src/components/Settings/Dhcp/Form.js index e65ab04a..da761892 100644 --- a/client/src/components/Settings/Dhcp/Form.js +++ b/client/src/components/Settings/Dhcp/Form.js @@ -1,25 +1,28 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { Field, reduxForm } from 'redux-form'; +import { withNamespaces, Trans } from 'react-i18next'; +import flow from 'lodash/flow'; + import { R_IPV4 } from '../../../helpers/constants'; const required = (value) => { if (value || value === 0) { return false; } - return 'Required field'; + return form_error_required; }; const ipv4 = (value) => { if (value && !new RegExp(R_IPV4).test(value)) { - return 'Invalid IPv4 format'; + return form_error_ip_format; } return false; }; const isPositive = (value) => { if ((value || value === 0) && (value <= 0)) { - return 'Must be greater than 0'; + return form_error_positive; } return false; }; @@ -43,7 +46,7 @@ const renderField = ({ const Form = (props) => { const { - handleSubmit, pristine, submitting, enabled, + handleSubmit, pristine, submitting, enabled, t, } = props; return ( @@ -51,25 +54,25 @@ const Form = (props) => {
- +
- + @@ -79,7 +82,7 @@ const Form = (props) => {
- +
{ component={renderField} type="text" className="form-control" - placeholder="Range start" + placeholder={t('dhcp_form_range_start')} validate={[ipv4, required]} disabled={!enabled} /> @@ -98,7 +101,7 @@ const Form = (props) => { component={renderField} type="text" className="form-control" - placeholder="Range end" + placeholder={t('dhcp_form_range_end')} validate={[ipv4, required]} disabled={!enabled} /> @@ -106,13 +109,13 @@ const Form = (props) => {
- + { className="btn btn-success btn-standart" disabled={pristine || submitting || !enabled} > - Save config + {t('save_config')} ); @@ -137,8 +140,10 @@ Form.propTypes = { pristine: PropTypes.bool, submitting: PropTypes.bool, enabled: PropTypes.bool, + t: PropTypes.func, }; -export default reduxForm({ - form: 'dhcpForm', -})(Form); +export default flow([ + withNamespaces(), + reduxForm({ form: 'dhcpForm' }), +])(Form); From 87b3c92f71bf4e9d20e569ccebcf2c5b93bacbc1 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 21 Dec 2018 15:52:59 +0300 Subject: [PATCH 17/45] Add /dhcp/interfaces API call to list available network interfaces. --- control.go | 1 + dhcp.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/control.go b/control.go index 0b00f08c..2671461d 100644 --- a/control.go +++ b/control.go @@ -720,6 +720,7 @@ func registerControlHandlers() { http.HandleFunc("/control/safesearch/disable", optionalAuth(ensurePOST(handleSafeSearchDisable))) http.HandleFunc("/control/safesearch/status", optionalAuth(ensureGET(handleSafeSearchStatus))) http.HandleFunc("/control/dhcp/status", optionalAuth(ensureGET(handleDHCPStatus))) + http.HandleFunc("/control/dhcp/interfaces", optionalAuth(ensureGET(handleDHCPInterfaces))) http.HandleFunc("/control/dhcp/set_config", optionalAuth(ensurePOST(handleDHCPSetConfig))) http.HandleFunc("/control/dhcp/find_active_dhcp", optionalAuth(ensurePOST(handleDHCPFindActiveServer))) } diff --git a/dhcp.go b/dhcp.go index 2cbc1cb6..3c0c5b31 100644 --- a/dhcp.go +++ b/dhcp.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "math/rand" + "net" "net/http" ) @@ -31,6 +32,76 @@ func handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { config.DHCP.Config = newconfig } +func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { + response := map[string]interface{}{} + + ifaces, err := net.Interfaces() + if err != nil { + httpError(w, http.StatusInternalServerError, "Couldn't get list of interfaces: %s", err) + return + } + + type address struct { + IP string + Netmask string + } + + type responseInterface struct { + Name string + MTU int + HardwareAddr string + Addresses []string + } + + for i := range ifaces { + if ifaces[i].Flags&net.FlagLoopback != 0 { + // it's a loopback, skip it + continue + } + if ifaces[i].Flags&net.FlagBroadcast == 0 { + // this interface doesn't support broadcast, skip it + continue + } + if ifaces[i].Flags&net.FlagPointToPoint != 0 { + // this interface is ppp, don't do dhcp over it + continue + } + iface := responseInterface{ + Name: ifaces[i].Name, + MTU: ifaces[i].MTU, + HardwareAddr: ifaces[i].HardwareAddr.String(), + } + addrs, err := ifaces[i].Addrs() + if err != nil { + httpError(w, http.StatusInternalServerError, "Failed to get addresses for interface %v: %s", ifaces[i].Name, err) + return + } + for _, addr := range addrs { + // ip, ok := addr.(*net.IPNet) + // if !ok { + // // not an IP address + // continue + // } + // responseAddr := address{ + // IP: ip.IP.String(), + // Netmask: ip.Mask.String(), + // } + iface.Addresses = append(iface.Addresses, addr.String()) + } + if len(iface.Addresses) == 0 { + // this interface has no addresses, skip it + continue + } + response[ifaces[i].Name] = iface + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + httpError(w, http.StatusInternalServerError, "Failed to marshal json with available interfaces: %s", err) + return + } +} + // TODO: implement func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { found := map[string]bool{ @@ -39,7 +110,7 @@ func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") err := json.NewEncoder(w).Encode(found) if err != nil { - httpError(w, http.StatusBadRequest, "Failed to marshal DHCP found json: %s", err) + httpError(w, http.StatusInternalServerError, "Failed to marshal DHCP found json: %s", err) return } } From c073f9db7b5bdb2c5fa75a70c1c034d2f4cfa3fe Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 21 Dec 2018 16:16:13 +0300 Subject: [PATCH 18/45] fixup of previous commit -- make json fields lowercase_underscored --- dhcp.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dhcp.go b/dhcp.go index 3c0c5b31..38284606 100644 --- a/dhcp.go +++ b/dhcp.go @@ -47,10 +47,10 @@ func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { } type responseInterface struct { - Name string - MTU int - HardwareAddr string - Addresses []string + Name string `json:"name"` + MTU int `json:"mtu"` + HardwareAddr string `json:"hardware_address"` + Addresses []string `json:"ip_addresses"` } for i := range ifaces { From 1270bbad1ab2454a6ad4b1c060ff0444fb43be2c Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 21 Dec 2018 16:29:13 +0300 Subject: [PATCH 19/45] fixup of previous commit -- Remove commented out code --- dhcp.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dhcp.go b/dhcp.go index 38284606..eb30ab75 100644 --- a/dhcp.go +++ b/dhcp.go @@ -77,15 +77,6 @@ func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { return } for _, addr := range addrs { - // ip, ok := addr.(*net.IPNet) - // if !ok { - // // not an IP address - // continue - // } - // responseAddr := address{ - // IP: ip.IP.String(), - // Netmask: ip.Mask.String(), - // } iface.Addresses = append(iface.Addresses, addr.String()) } if len(iface.Addresses) == 0 { From 712493aafd74d5c4a1e0ed32da6fb2bdbfffbc7f Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Mon, 24 Dec 2018 11:48:23 +0300 Subject: [PATCH 20/45] UI for DHCP interfaces --- client/src/__locales/en.json | 5 +- client/src/actions/index.js | 15 ++++ client/src/api/Api.js | 6 ++ client/src/components/Settings/Dhcp/Form.js | 87 +++++++++++++++++++- client/src/components/Settings/Dhcp/index.js | 2 + client/src/components/Settings/Settings.css | 13 +++ client/src/components/Settings/index.js | 1 + client/src/containers/Settings.js | 2 + client/src/reducers/index.js | 12 +++ 9 files changed, 139 insertions(+), 4 deletions(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 0ffe12f7..463ccc1a 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -149,5 +149,8 @@ "dhcp_form_range_start": "Range start", "dhcp_form_range_end": "Range end", "dhcp_form_lease_title": "DHCP lease time (in seconds)", - "dhcp_form_lease_input": "Lease duration" + "dhcp_form_lease_input": "Lease duration", + "dhcp_interface_select": "Select DHCP interface", + "dhcp_hardware_address": "Hardware address", + "dhcp_ip_addresses": "IP addresses" } diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 7e07c6a0..bb9d9bbb 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -538,6 +538,21 @@ export const getDhcpStatus = () => async (dispatch) => { } }; +export const getDhcpInterfacesRequest = createAction('GET_DHCP_INTERFACES_REQUEST'); +export const getDhcpInterfacesSuccess = createAction('GET_DHCP_INTERFACES_SUCCESS'); +export const getDhcpInterfacesFailure = createAction('GET_DHCP_INTERFACES_FAILURE'); + +export const getDhcpInterfaces = () => async (dispatch) => { + dispatch(getDhcpInterfacesRequest()); + try { + const interfaces = await apiClient.getDhcpInterfaces(); + dispatch(getDhcpInterfacesSuccess(interfaces)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getDhcpInterfacesFailure()); + } +}; + export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST'); export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS'); export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE'); diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 4aac23f5..2bb0277a 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -307,12 +307,18 @@ export default class Api { DHCP_STATUS = { path: 'dhcp/status', method: 'GET' }; DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' }; DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' }; + DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' }; getDhcpStatus() { const { path, method } = this.DHCP_STATUS; return this.makeRequest(path, method); } + getDhcpInterfaces() { + const { path, method } = this.DHCP_INTERFACES; + return this.makeRequest(path, method); + } + setDhcpConfig(config) { const { path, method } = this.DHCP_SET_CONFIG; const parameters = { diff --git a/client/src/components/Settings/Dhcp/Form.js b/client/src/components/Settings/Dhcp/Form.js index da761892..93140580 100644 --- a/client/src/components/Settings/Dhcp/Form.js +++ b/client/src/components/Settings/Dhcp/Form.js @@ -1,6 +1,7 @@ import React, { Fragment } from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { Field, reduxForm } from 'redux-form'; +import { Field, reduxForm, formValueSelector } from 'redux-form'; import { withNamespaces, Trans } from 'react-i18next'; import flow from 'lodash/flow'; @@ -44,14 +45,82 @@ const renderField = ({ ); -const Form = (props) => { +const renderInterfaces = (interfaces => ( + Object.keys(interfaces).map((item) => { + const option = interfaces[item]; + const { name } = option; + let interfaceIP = option.ip_addresses[0]; + + option.ip_addresses.forEach((ip) => { + if (!ip.includes(':')) { + interfaceIP = ip; + } + }); + + return ( + + ); + }) +)); + +const renderInterfaceValues = (interfaceValues => ( +
    +
  • + MTU: + {interfaceValues.mtu} +
  • +
  • + dhcp_hardware_address: + {interfaceValues.hardware_address} +
  • +
  • + dhcp_ip_addresses: + { + interfaceValues.ip_addresses + .map(ip => {ip}) + } +
  • +
+)); + +let Form = (props) => { const { - handleSubmit, pristine, submitting, enabled, t, + t, + handleSubmit, + pristine, + submitting, + enabled, + interfaces, + processing, + interfaceValue, } = props; return (
+
+ {!processing && interfaces && +
+
+
+ + + + {renderInterfaces(interfaces)} + +
+
+ {interfaceValue && +
+ {renderInterfaceValues(interfaces[interfaceValue])} +
+ } +
+ } +
+
@@ -140,9 +209,21 @@ Form.propTypes = { pristine: PropTypes.bool, submitting: PropTypes.bool, enabled: PropTypes.bool, + interfaces: PropTypes.object, + processing: PropTypes.bool, + interfaceValue: PropTypes.string, t: PropTypes.func, }; +const selector = formValueSelector('dhcpForm'); + +Form = connect((state) => { + const interfaceValue = selector(state, 'interface_name'); + return { + interfaceValue, + }; +})(Form); + export default flow([ withNamespaces(), reduxForm({ form: 'dhcpForm' }), diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js index 4b4eea67..e0317cd7 100644 --- a/client/src/components/Settings/Dhcp/index.js +++ b/client/src/components/Settings/Dhcp/index.js @@ -61,6 +61,8 @@ class Dhcp extends Component { onSubmit={this.handleFormSubmit} initialValues={dhcp.config} enabled={dhcp.config.enabled} + interfaces={dhcp.interfaces} + processing={dhcp.processingInterfaces} />
diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css index f9b50007..22e15b16 100644 --- a/client/src/components/Settings/Settings.css +++ b/client/src/components/Settings/Settings.css @@ -31,3 +31,16 @@ .form__message--error { color: #cd201f; } + +.interface__title { + font-size: 13px; + font-weight: 700; +} + +.interface__ip:after { + content: ", "; +} + +.interface__ip:last-child:after { + content: ""; +} diff --git a/client/src/components/Settings/index.js b/client/src/components/Settings/index.js index aafc64e2..24e56329 100644 --- a/client/src/components/Settings/index.js +++ b/client/src/components/Settings/index.js @@ -36,6 +36,7 @@ class Settings extends Component { componentDidMount() { this.props.initSettings(this.settings); this.props.getDhcpStatus(); + this.props.getDhcpInterfaces(); } handleUpstreamChange = (value) => { diff --git a/client/src/containers/Settings.js b/client/src/containers/Settings.js index 062fc2f2..7d46b751 100644 --- a/client/src/containers/Settings.js +++ b/client/src/containers/Settings.js @@ -8,6 +8,7 @@ import { addErrorToast, toggleDhcp, getDhcpStatus, + getDhcpInterfaces, setDhcpConfig, findActiveDhcp, } from '../actions'; @@ -28,6 +29,7 @@ const mapDispatchToProps = { addErrorToast, toggleDhcp, getDhcpStatus, + getDhcpInterfaces, setDhcpConfig, findActiveDhcp, }; diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 6f0f61d4..53ba15ca 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -272,6 +272,17 @@ const dhcp = handleActions({ return newState; }, + [actions.getDhcpInterfacesRequest]: state => ({ ...state, processingInterfaces: true }), + [actions.getDhcpInterfacesFailure]: state => ({ ...state, processingInterfaces: false }), + [actions.getDhcpInterfacesSuccess]: (state, { payload }) => { + const newState = { + ...state, + interfaces: payload, + processingInterfaces: false, + }; + return newState; + }, + [actions.findActiveDhcpRequest]: state => ({ ...state, processingStatus: true }), [actions.findActiveDhcpFailure]: state => ({ ...state, processingStatus: false }), [actions.findActiveDhcpSuccess]: (state, { payload }) => ({ @@ -289,6 +300,7 @@ const dhcp = handleActions({ }, { processing: true, processingStatus: false, + processingInterfaces: false, config: { enabled: false, }, From 73f71364b3841e26af2ac0132285301bce87f810 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Wed, 26 Dec 2018 19:27:19 +0300 Subject: [PATCH 21/45] Add interface name to dhcp config --- config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/config.go b/config.go index 4133aecb..89ec9e20 100644 --- a/config.go +++ b/config.go @@ -59,6 +59,7 @@ type dhcpState struct { // field ordering is important -- yaml fields will mirror ordering from here type dhcpConfig struct { Enabled bool `json:"enabled" yaml:"enabled"` + InterfaceName string `json:"interface_name" yaml:"interface_name"` // eth0, en0 and so on GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"` SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"` RangeStart string `json:"range_start" yaml:"range_start"` From 8b4a1ca71361c14f19dd1fbd8549235d9df0a2bb Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 28 Dec 2018 17:17:51 +0300 Subject: [PATCH 22/45] First implementation of DHCP server, compiles but not tested yet. --- config.go | 48 +---- dhcp.go | 36 +++- dhcpd/check_other_dhcp.go | 143 ++++++++++++++ dhcpd/dhcpd.go | 389 ++++++++++++++++++++++++++++++++++++++ dhcpd/filter_conn.go | 62 ++++++ dhcpd/helpers.go | 101 ++++++++++ dhcpd/standalone/main.go | 111 +++++++++++ go.mod | 2 + go.sum | 2 + 9 files changed, 847 insertions(+), 47 deletions(-) create mode 100644 dhcpd/check_other_dhcp.go create mode 100644 dhcpd/dhcpd.go create mode 100644 dhcpd/filter_conn.go create mode 100644 dhcpd/helpers.go create mode 100644 dhcpd/standalone/main.go diff --git a/config.go b/config.go index 89ec9e20..15ecffc1 100644 --- a/config.go +++ b/config.go @@ -6,8 +6,8 @@ import ( "os" "path/filepath" "sync" - "time" + "github.com/AdguardTeam/AdGuardHome/dhcpd" "github.com/AdguardTeam/AdGuardHome/dnsfilter" "github.com/AdguardTeam/AdGuardHome/dnsforward" "gopkg.in/yaml.v2" @@ -24,15 +24,15 @@ type configuration struct { ourConfigFilename string // Config filename (can be overriden via the command line arguments) ourBinaryDir string // Location of our directory, used to protect against CWD being somewhere else - BindHost string `yaml:"bind_host"` - BindPort int `yaml:"bind_port"` - AuthName string `yaml:"auth_name"` - AuthPass string `yaml:"auth_pass"` - Language string `yaml:"language"` // two-letter ISO 639-1 language code - DNS dnsConfig `yaml:"dns"` - Filters []filter `yaml:"filters"` - UserRules []string `yaml:"user_rules"` - DHCP dhcpState `yaml:"dhcp"` + BindHost string `yaml:"bind_host"` + BindPort int `yaml:"bind_port"` + AuthName string `yaml:"auth_name"` + AuthPass string `yaml:"auth_pass"` + Language string `yaml:"language"` // two-letter ISO 639-1 language code + DNS dnsConfig `yaml:"dns"` + Filters []filter `yaml:"filters"` + UserRules []string `yaml:"user_rules"` + DHCP dhcpd.ServerConfig `yaml:"dhcp"` sync.RWMutex `yaml:"-"` @@ -50,31 +50,6 @@ type dnsConfig struct { var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"} -// field ordering is important -- yaml fields will mirror ordering from here -type dhcpState struct { - Config dhcpConfig - Leases []dhcpLease -} - -// field ordering is important -- yaml fields will mirror ordering from here -type dhcpConfig struct { - Enabled bool `json:"enabled" yaml:"enabled"` - InterfaceName string `json:"interface_name" yaml:"interface_name"` // eth0, en0 and so on - GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"` - SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"` - RangeStart string `json:"range_start" yaml:"range_start"` - RangeEnd string `json:"range_end" yaml:"range_end"` - LeaseDuration uint64 `json:"lease_duration" yaml:"lease_duration"` // in seconds -} - -// field ordering is important -- yaml fields will mirror ordering from here -type dhcpLease struct { - HWAddr [6]byte `json:"mac" yaml:"hwaddr"` - IP string `json:"ip"` // json by default keeps IP uppercase but we need lowercase - Hostname string - Expires time.Time -} - // initialize to default values, will be changed later when reading config or parsing command line var config = configuration{ ourConfigFilename: "AdGuardHome.yaml", @@ -99,9 +74,6 @@ var config = configuration{ {Filter: dnsfilter.Filter{ID: 3}, Enabled: false, URL: "https://hosts-file.net/ad_servers.txt", Name: "hpHosts - Ad and Tracking servers only"}, {Filter: dnsfilter.Filter{ID: 4}, Enabled: false, URL: "http://www.malwaredomainlist.com/hostslist/hosts.txt", Name: "MalwareDomainList.com Hosts List"}, }, - DHCP: dhcpState{Config: dhcpConfig{ - LeaseDuration: 12 * 60 * 60, // in seconds - }}, SchemaVersion: currentSchemaVersion, } diff --git a/dhcp.go b/dhcp.go index eb30ab75..b86fa29c 100644 --- a/dhcp.go +++ b/dhcp.go @@ -2,15 +2,18 @@ package main import ( "encoding/json" - "math/rand" "net" "net/http" + + "github.com/AdguardTeam/AdGuardHome/dhcpd" ) +var dhcpServer = dhcpd.Server{} + func handleDHCPStatus(w http.ResponseWriter, r *http.Request) { status := map[string]interface{}{ - "config": config.DHCP.Config, - "leases": config.DHCP.Leases, + "config": config.DHCP, + "leases": dhcpServer.Leases(), } w.Header().Set("Content-Type", "application/json") @@ -22,14 +25,24 @@ func handleDHCPStatus(w http.ResponseWriter, r *http.Request) { } func handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { - newconfig := dhcpConfig{} + newconfig := dhcpd.ServerConfig{} err := json.NewDecoder(r.Body).Decode(&newconfig) if err != nil { httpError(w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err) return } - config.DHCP.Config = newconfig + if newconfig.Enabled { + err := dhcpServer.Start(&newconfig) + if err != nil { + httpError(w, http.StatusBadRequest, "Failed to start DHCP server: %s", err) + return + } + } + if !newconfig.Enabled { + dhcpServer.Stop() + } + config.DHCP = newconfig } func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { @@ -93,13 +106,18 @@ func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { } } -// TODO: implement +// implement func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { - found := map[string]bool{ - "found": rand.Intn(2) == 1, + found, err := dhcpd.CheckIfOtherDHCPServersPresent(config.DHCP.InterfaceName) + result := map[string]interface{}{ + "found": found, + } + if err != nil { + result["found"] = false + result["error"] = err } w.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(found) + err = json.NewEncoder(w).Encode(result) if err != nil { httpError(w, http.StatusInternalServerError, "Failed to marshal DHCP found json: %s", err) return diff --git a/dhcpd/check_other_dhcp.go b/dhcpd/check_other_dhcp.go new file mode 100644 index 00000000..7aed85ce --- /dev/null +++ b/dhcpd/check_other_dhcp.go @@ -0,0 +1,143 @@ +package dhcpd + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "math" + "net" + "os" + "time" + + "github.com/krolaw/dhcp4" +) + +func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) { + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return false, wrapErrPrint(err, "Couldn't find interface by name %s", ifaceName) + } + + // get ipv4 address of an interface + ifaceIPNet := getIfaceIPv4(iface) + if ifaceIPNet == nil { + return false, fmt.Errorf("Couldn't find IPv4 address of interface %s %+v", ifaceName, iface) + } + + srcIP := ifaceIPNet.IP + src := net.JoinHostPort(srcIP.String(), "68") + dst := "255.255.255.255:67" + + // form a DHCP request packet, try to emulate existing client as much as possible + xId := make([]byte, 8) + n, err := rand.Read(xId) + if n != 8 && err == nil { + err = fmt.Errorf("Generated less than 8 bytes") + } + if err != nil { + return false, wrapErrPrint(err, "Couldn't generate 8 random bytes") + } + hostname, err := os.Hostname() + if err != nil { + return false, wrapErrPrint(err, "Couldn't get hostname") + } + requestList := []byte{ + byte(dhcp4.OptionSubnetMask), + byte(dhcp4.OptionClasslessRouteFormat), + byte(dhcp4.OptionRouter), + byte(dhcp4.OptionDomainNameServer), + byte(dhcp4.OptionDomainName), + byte(dhcp4.OptionDomainSearch), + 252, // private/proxy autodiscovery + 95, // LDAP + byte(dhcp4.OptionNetBIOSOverTCPIPNameServer), + byte(dhcp4.OptionNetBIOSOverTCPIPNodeType), + } + maxUDPsizeRaw := make([]byte, 2) + binary.BigEndian.PutUint16(maxUDPsizeRaw, 1500) + leaseTimeRaw := make([]byte, 4) + leaseTime := uint32(math.RoundToEven(time.Duration(time.Hour * 24 * 90).Seconds())) + binary.BigEndian.PutUint32(leaseTimeRaw, leaseTime) + options := []dhcp4.Option{ + {dhcp4.OptionParameterRequestList, requestList}, + {dhcp4.OptionMaximumDHCPMessageSize, maxUDPsizeRaw}, + {dhcp4.OptionClientIdentifier, append([]byte{0x01}, iface.HardwareAddr...)}, + {dhcp4.OptionIPAddressLeaseTime, leaseTimeRaw}, + {dhcp4.OptionHostName, []byte(hostname)}, + } + packet := dhcp4.RequestPacket(dhcp4.Discover, iface.HardwareAddr, nil, xId, false, options) + + // resolve 0.0.0.0:68 + udpAddr, err := net.ResolveUDPAddr("udp4", src) + if err != nil { + return false, wrapErrPrint(err, "Couldn't resolve UDP address %s", src) + } + // spew.Dump(udpAddr, err) + + if !udpAddr.IP.To4().Equal(srcIP) { + return false, wrapErrPrint(err, "Resolved UDP address is not %s", src) + } + + // resolve 255.255.255.255:67 + dstAddr, err := net.ResolveUDPAddr("udp4", dst) + if err != nil { + return false, wrapErrPrint(err, "Couldn't resolve UDP address %s", dst) + } + + // bind to 0.0.0.0:68 + trace("Listening to udp4 %+v", udpAddr) + c, err := net.ListenPacket("udp4", src) + if c != nil { + defer c.Close() + } + // spew.Dump(c, err) + // spew.Printf("net.ListenUDP returned %v, %v\n", c, err) + if err != nil { + return false, wrapErrPrint(err, "Couldn't listen to %s", src) + } + + // send to 255.255.255.255:67 + n, err = c.WriteTo(packet, dstAddr) + // spew.Dump(n, err) + if err != nil { + return false, wrapErrPrint(err, "Couldn't send a packet to %s", dst) + } + + // wait for answer + trace("Waiting %v for an answer", defaultDiscoverTime) + // TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts + b := make([]byte, 1500) + c.SetReadDeadline(time.Now().Add(defaultDiscoverTime)) + n, _, err = c.ReadFrom(b) + if isTimeout(err) { + // timed out -- no DHCP servers + return false, nil + } + if err != nil { + return false, wrapErrPrint(err, "Couldn't receive packet") + } + if n > 0 { + b = b[:n] + } + // spew.Dump(n, fromAddr, err, b) + + if n < 240 { + // packet too small for dhcp + return false, wrapErrPrint(err, "got packet that's too small for DHCP") + } + + response := dhcp4.Packet(b[:n]) + if response.HLen() > 16 { + // invalid size + return false, wrapErrPrint(err, "got malformed packet with HLen() > 16") + } + + parsedOptions := response.ParseOptions() + _, ok := parsedOptions[dhcp4.OptionDHCPMessageType] + if !ok { + return false, wrapErrPrint(err, "got malformed packet without DHCP message type") + } + + // that's a DHCP server there + return true, nil +} diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go new file mode 100644 index 00000000..8dc500a1 --- /dev/null +++ b/dhcpd/dhcpd.go @@ -0,0 +1,389 @@ +package dhcpd + +import ( + "bytes" + "fmt" + "log" + "net" + "time" + + "github.com/krolaw/dhcp4" +) + +const defaultDiscoverTime = time.Second * 3 + +// field ordering is important -- yaml fields will mirror ordering from here +type Lease struct { + hwaddr net.HardwareAddr `json:"mac" yaml:"hwaddr"` + ip net.IP `json:"ip"` + expiry time.Time `json:"expires"` +} + +// field ordering is important -- yaml fields will mirror ordering from here +type ServerConfig struct { + Enabled bool `json:"enabled" yaml:"enabled"` + InterfaceName string `json:"interface_name" yaml:"interface_name"` // eth0, en0 and so on + GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"` + SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"` + RangeStart string `json:"range_start" yaml:"range_start"` + RangeEnd string `json:"range_end" yaml:"range_end"` + LeaseDuration uint `json:"lease_duration" yaml:"lease_duration"` // in seconds +} + +type Server struct { + conn *filterConn // listening UDP socket + + ipnet *net.IPNet // if interface name changes, this needs to be reset + + // leases + leases []*Lease + leaseStart net.IP // parsed from config RangeStart + leaseStop net.IP // parsed from config RangeEnd + leaseTime time.Duration // parsed from config LeaseDuration + leaseOptions dhcp4.Options // parsed from config GatewayIP and SubnetMask + + // IP address pool -- if entry is in the pool, then it's attached to a lease + IPpool map[[4]byte]net.HardwareAddr + + ServerConfig +} + +// Start will listen on port 67 and serve DHCP requests. +// Even though config can be nil, it is not optional (at least for now), since there are no default values (yet). +func (s *Server) Start(config *ServerConfig) error { + if config != nil { + s.ServerConfig = *config + } + + iface, err := net.InterfaceByName(s.InterfaceName) + if err != nil { + s.closeConn() // in case it was already started + return wrapErrPrint(err, "Couldn't find interface by name %s", s.InterfaceName) + } + + // get ipv4 address of an interface + s.ipnet = getIfaceIPv4(iface) + if s.ipnet == nil { + s.closeConn() // in case it was already started + return wrapErrPrint(err, "Couldn't find IPv4 address of interface %s %+v", s.InterfaceName, iface) + } + + if s.LeaseDuration == 0 { + s.leaseTime = time.Hour * 2 + s.LeaseDuration = uint(s.leaseTime.Seconds()) + } else { + s.leaseTime = time.Second * time.Duration(s.LeaseDuration) + } + + s.leaseStart, err = parseIPv4(s.RangeStart) + if err != nil { + s.closeConn() // in case it was already started + return wrapErrPrint(err, "Failed to parse range start address %s", s.RangeStart) + } + + s.leaseStop, err = parseIPv4(s.RangeEnd) + if err != nil { + s.closeConn() // in case it was already started + return wrapErrPrint(err, "Failed to parse range end address %s", s.RangeEnd) + } + + subnet, err := parseIPv4(s.SubnetMask) + if err != nil { + s.closeConn() // in case it was already started + return wrapErrPrint(err, "Failed to parse subnet mask %s", s.SubnetMask) + } + + // if !bytes.Equal(subnet, s.ipnet.Mask) { + // s.closeConn() // in case it was already started + // return wrapErrPrint(err, "specified subnet mask %s does not meatch interface %s subnet mask %s", s.SubnetMask, s.InterfaceName, s.ipnet.Mask) + // } + + router, err := parseIPv4(s.GatewayIP) + if err != nil { + s.closeConn() // in case it was already started + return wrapErrPrint(err, "Failed to parse gateway IP %s", s.GatewayIP) + } + + s.leaseOptions = dhcp4.Options{ + dhcp4.OptionSubnetMask: subnet, + dhcp4.OptionRouter: router, + dhcp4.OptionDomainNameServer: s.ipnet.IP, + } + + // TODO: don't close if interface and addresses are the same + if s.conn != nil { + s.closeConn() + } + + c, err := newFilterConn(*iface, ":67") // it has to be bound to 0.0.0.0:67, otherwise it won't see DHCP discover/request packets + if err != nil { + return wrapErrPrint(err, "Couldn't start listening socket on 0.0.0.0:67") + } + + s.conn = c + + go func() { + // operate on c instead of c.conn because c.conn can change over time + err := dhcp4.Serve(c, s) + if err != nil { + log.Printf("dhcp4.Serve() returned with error: %s", err) + } + c.Close() // in case Serve() exits for other reason than listening socket closure + }() + + return nil +} + +func (s *Server) Stop() error { + if s.conn == nil { + // nothing to do, return silently + return nil + } + err := s.closeConn() + if err != nil { + return wrapErrPrint(err, "Couldn't close UDP listening socket") + } + + return nil +} + +// closeConn will close the connection and set it to zero +func (s *Server) closeConn() error { + if s.conn == nil { + return nil + } + err := s.conn.Close() + s.conn = nil + return err +} + +func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) { + // WARNING: do not remove copy() + // the given hwaddr by p.CHAddr() in the packet survives only during ServeDHCP() call + // since we need to retain it we need to make our own copy + hwaddrCOW := p.CHAddr() + hwaddr := make(net.HardwareAddr, len(hwaddrCOW)) + copy(hwaddr, hwaddrCOW) + foundLease := s.locateLease(p) + if foundLease != nil { + // trace("found lease for %s: %+v", hwaddr, foundLease) + return foundLease, nil + } + // not assigned a lease, create new one, find IP from LRU + trace("Lease not found for %s: creating new one", hwaddr) + ip, err := s.findFreeIP(p, hwaddr) + if err != nil { + return nil, wrapErrPrint(err, "Couldn't find free IP for the lease %s", hwaddr.String()) + } + trace("Assigning to %s IP address %s", hwaddr, ip.String()) + lease := &Lease{hwaddr: hwaddr, ip: ip} + s.leases = append(s.leases, lease) + return lease, nil +} + +func (s *Server) locateLease(p dhcp4.Packet) *Lease { + hwaddr := p.CHAddr() + for i := range s.leases { + if bytes.Equal([]byte(hwaddr), []byte(s.leases[i].hwaddr)) { + // trace("bytes.Equal(%s, %s) returned true", hwaddr, s.leases[i].hwaddr) + return s.leases[i] + } + } + return nil +} + +func (s *Server) findFreeIP(p dhcp4.Packet, hwaddr net.HardwareAddr) (net.IP, error) { + // if IP pool is nil, lazy initialize it + if s.IPpool == nil { + s.IPpool = make(map[[4]byte]net.HardwareAddr) + } + + // go from start to end, find unreserved IP + var foundIP net.IP + for i := 0; i < dhcp4.IPRange(s.leaseStart, s.leaseStop); i++ { + newIP := dhcp4.IPAdd(s.leaseStart, i) + foundHWaddr := s.getIPpool(newIP) + trace("tried IP %v, got hwaddr %v", newIP, foundHWaddr) + if foundHWaddr != nil && len(foundHWaddr) != 0 { + // if !bytes.Equal(foundHWaddr, hwaddr) { + // trace("SHOULD NOT HAPPEN: hwaddr in IP pool %s is not equal to hwaddr in lease %s", foundHWaddr, hwaddr) + // } + trace("will try again") + continue + } + foundIP = newIP + break + } + + if foundIP == nil { + // TODO: LRU + return nil, fmt.Errorf("Couldn't find free entry in IP pool") + } + + s.reserveIP(foundIP, hwaddr) + + return foundIP, nil +} + +func (s *Server) getIPpool(ip net.IP) net.HardwareAddr { + rawIP := []byte(ip) + IP4 := [4]byte{rawIP[0], rawIP[1], rawIP[2], rawIP[3]} + return s.IPpool[IP4] +} + +func (s *Server) reserveIP(ip net.IP, hwaddr net.HardwareAddr) { + rawIP := []byte(ip) + IP4 := [4]byte{rawIP[0], rawIP[1], rawIP[2], rawIP[3]} + s.IPpool[IP4] = hwaddr +} + +func (s *Server) unreserveIP(ip net.IP) { + rawIP := []byte(ip) + IP4 := [4]byte{rawIP[0], rawIP[1], rawIP[2], rawIP[3]} + delete(s.IPpool, IP4) +} + +func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dhcp4.Options) dhcp4.Packet { + trace("Got %v message", msgType) + trace("Leases:") + for i, lease := range s.leases { + trace("Lease #%d: hwaddr %s, ip %s, expiry %s", i, lease.hwaddr, lease.ip, lease.expiry) + } + trace("IP pool:") + for ip, hwaddr := range s.IPpool { + trace("IP pool entry %s -> %s", net.IPv4(ip[0], ip[1], ip[2], ip[3]), hwaddr) + } + // spew.Dump(s.leases, s.IPpool) + // log.Printf("Called with msgType = %v, options = %+v", msgType, options) + // spew.Dump(p) + // log.Printf("%14s %v", "p.Broadcast", p.Broadcast()) // false + // log.Printf("%14s %v", "p.CHAddr", p.CHAddr()) // 2c:f0:a2:f2:31:00 + // log.Printf("%14s %v", "p.CIAddr", p.CIAddr()) // 0.0.0.0 + // log.Printf("%14s %v", "p.Cookie", p.Cookie()) // [99 130 83 99] + // log.Printf("%14s %v", "p.File", p.File()) // [] + // log.Printf("%14s %v", "p.Flags", p.Flags()) // [0 0] + // log.Printf("%14s %v", "p.GIAddr", p.GIAddr()) // 0.0.0.0 + // log.Printf("%14s %v", "p.HLen", p.HLen()) // 6 + // log.Printf("%14s %v", "p.HType", p.HType()) // 1 + // log.Printf("%14s %v", "p.Hops", p.Hops()) // 0 + // log.Printf("%14s %v", "p.OpCode", p.OpCode()) // BootRequest + // log.Printf("%14s %v", "p.Options", p.Options()) // [53 1 1 55 10 1 121 3 6 15 119 252 95 44 46 57 2 5 220 61 7 1 44 240 162 242 49 0 51 4 0 118 167 0 12 4 119 104 109 100 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] + // log.Printf("%14s %v", "p.ParseOptions", p.ParseOptions()) // map[OptionParameterRequestList:[1 121 3 6 15 119 252 95 44 46] OptionDHCPMessageType:[1] OptionMaximumDHCPMessageSize:[5 220] OptionClientIdentifier:[1 44 240 162 242 49 0] OptionIPAddressLeaseTime:[0 118 167 0] OptionHostName:[119 104 109 100]] + // log.Printf("%14s %v", "p.SIAddr", p.SIAddr()) // 0.0.0.0 + // log.Printf("%14s %v", "p.SName", p.SName()) // [] + // log.Printf("%14s %v", "p.Secs", p.Secs()) // [0 8] + // log.Printf("%14s %v", "p.XId", p.XId()) // [211 184 20 44] + // log.Printf("%14s %v", "p.YIAddr", p.YIAddr()) // 0.0.0.0 + + switch msgType { + case dhcp4.Discover: // Broadcast Packet From Client - Can I have an IP? + // find a lease, but don't update lease time + trace("Got from client: Discover") + lease, err := s.reserveLease(p) + if err != nil { + trace("Couldn't find free lease: %s", err) + // couldn't find lease, don't respond + return nil + } + reply := dhcp4.ReplyPacket(p, dhcp4.Offer, s.ipnet.IP, lease.ip, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) + trace("Replying with offer: offered IP %v for %v with options %+v", lease.ip, s.leaseTime, reply.ParseOptions()) + return reply + case dhcp4.Request: // Broadcast From Client - I'll take that IP (Also start for renewals) + // start/renew a lease -- update lease time + // some clients (OSX) just go right ahead and do Request first from previously known IP, if they get NAK, they restart full cycle with Discover then Request + trace("Got from client: Request") + if server, ok := options[dhcp4.OptionServerIdentifier]; ok && !net.IP(server).Equal(s.ipnet.IP) { + trace("Request message not for this DHCP server (%v vs %v)", p, server, s.ipnet.IP) + return nil // Message not for this dhcp server + } + + reqIP := net.IP(options[dhcp4.OptionRequestedIPAddress]) + if reqIP == nil { + reqIP = net.IP(p.CIAddr()) + } + + if reqIP.To4() == nil { + trace("Replying with NAK: request IP isn't valid IPv4: %s", reqIP) + return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil) + } + + if reqIP.Equal(net.IPv4zero) { + trace("Replying with NAK: request IP is 0.0.0.0") + return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil) + } + + trace("requested IP is %s", reqIP) + lease, err := s.reserveLease(p) + if err != nil { + trace("Couldn't find free lease: %s", err) + // couldn't find lease, don't respond + return nil + } + + if lease.ip.Equal(reqIP) { + // IP matches lease IP, nothing else to do + lease.expiry = time.Now().Add(s.leaseTime) + trace("Replying with ACK: request IP matches lease IP, nothing else to do. IP %v for %v", lease.ip, p.CHAddr()) + return dhcp4.ReplyPacket(p, dhcp4.ACK, s.ipnet.IP, lease.ip, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) + } + + // + // requested IP different from lease + // + + trace("lease IP is different from requested IP: %s vs %s", lease.ip, reqIP) + + hwaddr := s.getIPpool(reqIP) + if hwaddr == nil { + // not in pool, check if it's in DHCP range + if dhcp4.IPInRange(s.leaseStart, s.leaseStop, reqIP) { + // okay, we can give it to our client -- it's in our DHCP range and not taken, so let them use their IP + trace("Replying with ACK: request IP %v is not taken, so assigning lease IP %v to it, for %v", reqIP, lease.ip, p.CHAddr()) + s.unreserveIP(lease.ip) + lease.ip = reqIP + s.reserveIP(reqIP, p.CHAddr()) + lease.expiry = time.Now().Add(s.leaseTime) + return dhcp4.ReplyPacket(p, dhcp4.ACK, s.ipnet.IP, lease.ip, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) + } + } + + if hwaddr != nil && !bytes.Equal(hwaddr, lease.hwaddr) { + log.Printf("SHOULD NOT HAPPEN: IP pool hwaddr does not match lease hwaddr: %s vs %s", hwaddr, lease.hwaddr) + } + + // requsted IP is not sufficient, reply with NAK + if hwaddr != nil { + trace("Replying with NAK: request IP %s is taken, asked by %v", reqIP, p.CHAddr()) + return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil) + } + + // requested IP is outside of DHCP range + trace("Replying with NAK: request IP %s is outside of DHCP range [%s, %s], asked by %v", reqIP, s.leaseStart, s.leaseStop, p.CHAddr()) + return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil) + case dhcp4.Decline: // Broadcast From Client - Sorry I can't use that IP + trace("Got from client: Decline") + + case dhcp4.Release: // From Client, I don't need that IP anymore + trace("Got from client: Release") + + case dhcp4.Inform: // From Client, I have this IP and there's nothing you can do about it + trace("Got from client: Inform") + // do nothing + + // from server -- ignore those but enumerate just in case + case dhcp4.Offer: // Broadcast From Server - Here's an IP + log.Printf("SHOULD NOT HAPPEN -- FROM ANOTHER DHCP SERVER: Offer") + case dhcp4.ACK: // From Server, Yes you can have that IP + log.Printf("SHOULD NOT HAPPEN -- FROM ANOTHER DHCP SERVER: ACK") + case dhcp4.NAK: // From Server, No you cannot have that IP + log.Printf("SHOULD NOT HAPPEN -- FROM ANOTHER DHCP SERVER: NAK") + default: + log.Printf("Unknown DHCP packet detected, ignoring: %v", msgType) + return nil + } + return nil +} + +func (s *Server) Leases() []*Lease { + return s.leases +} diff --git a/dhcpd/filter_conn.go b/dhcpd/filter_conn.go new file mode 100644 index 00000000..cd943ab1 --- /dev/null +++ b/dhcpd/filter_conn.go @@ -0,0 +1,62 @@ +package dhcpd + +import ( + "net" + + "github.com/joomcode/errorx" + "golang.org/x/net/ipv4" +) + +// TODO: on windows, controlmessage does not work, try to find out another way +// https://github.com/golang/net/blob/master/ipv4/payload.go#L13 + +type filterConn struct { + iface net.Interface + conn *ipv4.PacketConn + // cm *ipv4.ControlMessage +} + +func newFilterConn(iface net.Interface, address string) (*filterConn, error) { + c, err := net.ListenPacket("udp4", address) + if err != nil { + return nil, errorx.Decorate(err, "Couldn't listen to %s on UDP4", address) + } + + p := ipv4.NewPacketConn(c) + err = p.SetControlMessage(ipv4.FlagInterface, true) + if err != nil { + c.Close() + return nil, errorx.Decorate(err, "Couldn't set control message FlagInterface on connection") + } + + return &filterConn{iface: iface, conn: p}, nil +} + +func (f *filterConn) ReadFrom(b []byte) (int, net.Addr, error) { + for { // read until we find a suitable packet + n, cm, addr, err := f.conn.ReadFrom(b) + if err != nil { + return 0, addr, errorx.Decorate(err, "Error when reading from socket") + } + if cm == nil { + // no controlmessage was passed, so pass the packet to the caller + return n, addr, nil + } + if cm.IfIndex == f.iface.Index { + return n, addr, nil + } + // packet doesn't match criteria, drop it + } + return 0, nil, nil +} + +func (f *filterConn) WriteTo(b []byte, addr net.Addr) (int, error) { + cm := ipv4.ControlMessage{ + IfIndex: f.iface.Index, + } + return f.conn.WriteTo(b, &cm, addr) +} + +func (f *filterConn) Close() error { + return f.conn.Close() +} diff --git a/dhcpd/helpers.go b/dhcpd/helpers.go new file mode 100644 index 00000000..cf721f31 --- /dev/null +++ b/dhcpd/helpers.go @@ -0,0 +1,101 @@ +package dhcpd + +import ( + "fmt" + "log" + "net" + "os" + "path" + "runtime" + "strings" + + "github.com/joomcode/errorx" +) + +func trace(format string, args ...interface{}) { + pc := make([]uintptr, 10) // at least 1 entry needed + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + var buf strings.Builder + buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name()))) + text := fmt.Sprintf(format, args...) + buf.WriteString(text) + if len(text) == 0 || text[len(text)-1] != '\n' { + buf.WriteRune('\n') + } + fmt.Fprint(os.Stderr, buf.String()) +} + +func isTimeout(err error) bool { + operr, ok := err.(*net.OpError) + if !ok { + return false + } + return operr.Timeout() +} + +// return first IPv4 address of an interface, if there is any +func getIfaceIPv4(iface *net.Interface) *net.IPNet { + ifaceAddrs, err := iface.Addrs() + if err != nil { + panic(err) + } + + for _, addr := range ifaceAddrs { + ipnet, ok := addr.(*net.IPNet) + if !ok { + // not an IPNet, should not happen + log.Fatalf("SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet", addr) + } + + if ipnet.IP.To4() == nil { + log.Printf("Got IP that is not IPv4: %v", ipnet.IP) + continue + } + + log.Printf("Got IP that is IPv4: %v", ipnet.IP) + return &net.IPNet{ + IP: ipnet.IP.To4(), + Mask: ipnet.Mask, + } + } + return nil +} + +func isConnClosed(err error) bool { + if err == nil { + return false + } + nerr, ok := err.(*net.OpError) + if !ok { + return false + } + + if strings.Contains(nerr.Err.Error(), "use of closed network connection") { + return true + } + + return false +} + +func wrapErrPrint(err error, message string, args ...interface{}) error { + var errx error + if err == nil { + errx = fmt.Errorf(message, args...) + } else { + errx = errorx.Decorate(err, message, args...) + } + log.Println(errx.Error()) + return errx +} + +func parseIPv4(text string) (net.IP, error) { + result := net.ParseIP(text) + if result == nil { + return nil, fmt.Errorf("%s is not an IP address", text) + } + if result.To4() == nil { + return nil, fmt.Errorf("%s is not an IPv4 address", text) + } + return result.To4(), nil +} diff --git a/dhcpd/standalone/main.go b/dhcpd/standalone/main.go new file mode 100644 index 00000000..f6dac3ac --- /dev/null +++ b/dhcpd/standalone/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "log" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/AdguardTeam/AdGuardHome/dhcpd" + "github.com/krolaw/dhcp4" +) + +func main() { + if len(os.Args) < 2 { + log.Printf("Usage: %s ", os.Args[0]) + os.Exit(64) + } + + ifaceName := os.Args[1] + present, err := dhcpd.CheckIfOtherDHCPServersPresent(ifaceName) + if err != nil { + panic(err) + } + log.Printf("Found DHCP server? %v", present) + if present { + log.Printf("Will not start DHCP server because there's already running one on the network") + os.Exit(1) + } + + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + panic(err) + } + + // get ipv4 address of an interface + ifaceIPNet := getIfaceIPv4(iface) + if ifaceIPNet == nil { + panic(err) + } + + // append 10 to server's IP address as start + start := dhcp4.IPAdd(ifaceIPNet.IP, 10) + // lease range is 100 IP's, but TODO: don't go beyond end of subnet mask + stop := dhcp4.IPAdd(start, 100) + + server := dhcpd.Server{} + config := dhcpd.ServerConfig{ + InterfaceName: ifaceName, + RangeStart: start.String(), + RangeEnd: stop.String(), + SubnetMask: "255.255.255.0", + GatewayIP: "192.168.7.1", + } + log.Printf("Starting DHCP server") + err = server.Start(&config) + if err != nil { + panic(err) + } + + time.Sleep(time.Second) + log.Printf("Stopping DHCP server") + err = server.Stop() + if err != nil { + panic(err) + } + log.Printf("Starting DHCP server") + err = server.Start(&config) + if err != nil { + panic(err) + } + log.Printf("Starting DHCP server while it's already running") + err = server.Start(&config) + if err != nil { + panic(err) + } + log.Printf("Now serving DHCP") + signal_channel := make(chan os.Signal) + signal.Notify(signal_channel, syscall.SIGINT, syscall.SIGTERM) + <-signal_channel + +} + +// return first IPv4 address of an interface, if there is any +func getIfaceIPv4(iface *net.Interface) *net.IPNet { + ifaceAddrs, err := iface.Addrs() + if err != nil { + panic(err) + } + + for _, addr := range ifaceAddrs { + ipnet, ok := addr.(*net.IPNet) + if !ok { + // not an IPNet, should not happen + log.Fatalf("SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet", addr) + } + + if ipnet.IP.To4() == nil { + log.Printf("Got IP that is not IPv4: %v", ipnet.IP) + continue + } + + log.Printf("Got IP that is IPv4: %v", ipnet.IP) + return &net.IPNet{ + IP: ipnet.IP.To4(), + Mask: ipnet.Mask, + } + } + return nil +} diff --git a/go.mod b/go.mod index 36ddb6a2..bdc4c0d3 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,13 @@ require ( github.com/ameshkov/dnscrypt v1.0.1 github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 github.com/bluele/gcache v0.0.0-20171010155617-472614239ac7 + github.com/davecgh/go-spew v1.1.1 github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-test/deep v1.0.1 github.com/gobuffalo/packr v1.19.0 github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86 github.com/joomcode/errorx v0.1.0 + github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 github.com/miekg/dns v1.1.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.8.0 diff --git a/go.sum b/go.sum index 1a32fbbb..4c18efc7 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/joomcode/errorx v0.1.0 h1:QmJMiI1DE1UFje2aI1ZWO/VMT5a32qBoXUclGOt8vsc github.com/joomcode/errorx v0.1.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32BM74ph7bPgqMztWopMYKgVyEho= +github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o= github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc= github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o= From 4aea91a70c1bcc5ffeb93e282f54291ab7d4c28a Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Fri, 28 Dec 2018 19:48:02 +0300 Subject: [PATCH 23/45] Refresh status button --- client/src/__locales/en.json | 57 +++++++++--------- client/src/__locales/ja.json | 62 ++++++++++++++------ client/src/__locales/pt-br.json | 28 ++++++++- client/src/__locales/sv.json | 28 ++++++++- client/src/actions/index.js | 5 +- client/src/components/Settings/Dhcp/Form.js | 31 +++++----- client/src/components/Settings/Dhcp/index.js | 46 +++++++++------ 7 files changed, 172 insertions(+), 85 deletions(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 463ccc1a..243ed012 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -1,4 +1,30 @@ { + "refresh_status": "Refresh status", + "save_config": "Save config", + "enabled_dhcp": "DHCP server enabled", + "disabled_dhcp": "DHCP server disabled", + "dhcp_title": "DHCP server", + "dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.", + "dhcp_enable": "Enable DHCP server", + "dhcp_disable": "Disable DHCP server", + "dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.", + "dhcp_found": "Found active DHCP servers found on the network. It is not safe to enable the built-in DHCP server.", + "dhcp_leases": "DHCP leases", + "dhcp_leases_not_found": "No DHCP leases found", + "dhcp_config_saved": "Saved DHCP server config", + "form_error_required": "Required field", + "form_error_ip_format": "Invalid IPv4 format", + "form_error_positive": "Must be greater than 0", + "dhcp_form_gateway_input": "Gateway IP", + "dhcp_form_subnet_input": "Subnet mask", + "dhcp_form_range_title": "Range of IP addresses", + "dhcp_form_range_start": "Range start", + "dhcp_form_range_end": "Range end", + "dhcp_form_lease_title": "DHCP lease time (in seconds)", + "dhcp_form_lease_input": "Lease duration", + "dhcp_interface_select": "Select DHCP interface", + "dhcp_hardware_address": "Hardware address", + "dhcp_ip_addresses": "IP addresses", "back": "Back", "dashboard": "Dashboard", "settings": "Settings", @@ -89,7 +115,7 @@ "example_upstream_regular": "regular DNS (over UDP)", "example_upstream_dot": "encrypted DNS-over-TLS<\/a>", "example_upstream_doh": "encrypted DNS-over-HTTPS<\/a>", - "example_upstream_sdns": "you can use DNS Stamps for DNSCrypt or DNS-over-HTTPS resolvers", + "example_upstream_sdns": "you can use DNS Stamps<\/a> for DNSCrypt<\/a> or DNS-over-HTTPS<\/a> resolvers", "example_upstream_tcp": "regular DNS (over TCP)", "all_filters_up_to_date_toast": "All filters are already up-to-date", "updated_upstream_dns_toast": "Updated the upstream DNS servers", @@ -127,30 +153,5 @@ "category_label": "Category", "rule_label": "Rule", "filter_label": "Filter", - "unknown_filter": "Unknown filter {{filterId}}", - "refresh_status": "Refresh status", - "save_config": "Save config", - "enabled_dhcp": "DHCP server enabled", - "disabled_dhcp": "DHCP server disabled", - "dhcp_title": "DHCP server", - "dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.", - "dhcp_enable": "Enable DHCP server", - "dhcp_disable": "Disable DHCP server", - "dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.", - "dhcp_leases": "DHCP leases", - "dhcp_leases_not_found": "No DHCP leases found", - "dhcp_config_saved": "Saved DHCP server config", - "form_error_required": "Required field", - "form_error_ip_format": "Invalid IPv4 format", - "form_error_positive": "Must be greater than 0", - "dhcp_form_gateway_input": "Gateway IP", - "dhcp_form_subnet_input": "Subnet mask", - "dhcp_form_range_title": "Range of IP addresses", - "dhcp_form_range_start": "Range start", - "dhcp_form_range_end": "Range end", - "dhcp_form_lease_title": "DHCP lease time (in seconds)", - "dhcp_form_lease_input": "Lease duration", - "dhcp_interface_select": "Select DHCP interface", - "dhcp_hardware_address": "Hardware address", - "dhcp_ip_addresses": "IP addresses" -} + "unknown_filter": "Unknown filter {{filterId}}" +} \ No newline at end of file diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json index ab4b766c..5af7af43 100644 --- a/client/src/__locales/ja.json +++ b/client/src/__locales/ja.json @@ -1,10 +1,32 @@ { + "refresh_status": "\u30b9\u30c6\u30fc\u30bf\u30b9\u3092\u6700\u65b0\u306b\u3059\u308b", + "save_config": "\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3059\u308b", + "enabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f", + "disabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f", + "dhcp_title": "DHCP\u30b5\u30fc\u30d0", + "dhcp_description": "\u3042\u306a\u305f\u306e\u30eb\u30fc\u30bf\u304cDHCP\u306e\u8a2d\u5b9a\u3092\u63d0\u4f9b\u3057\u3066\u3044\u306a\u3044\u306e\u306a\u3089\u3001AdGuard\u306b\u5185\u8535\u3055\u308c\u305fDHCP\u30b5\u30fc\u30d0\u3092\u5229\u7528\u3067\u304d\u307e\u3059\u3002", + "dhcp_enable": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3059\u308b", + "dhcp_disable": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3059\u308b", + "dhcp_not_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u5185\u8535\u3055\u308c\u305fDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u5b89\u5168\u3067\u3059\u3002", + "dhcp_leases": "DHCP\u5272\u5f53", + "dhcp_leases_not_found": "DHCP\u5272\u5f53\u306f\u3042\u308a\u307e\u305b\u3093", + "dhcp_config_saved": "DHCP\u30b5\u30fc\u30d0\u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f", + "form_error_required": "\u5fc5\u9808\u9805\u76ee\u3067\u3059", + "form_error_ip_format": "IPv4\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u306f\u3042\u308a\u307e\u305b\u3093", + "form_error_positive": "0\u3088\u308a\u5927\u304d\u3044\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", + "dhcp_form_gateway_input": "\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4IP", + "dhcp_form_subnet_input": "\u30b5\u30d6\u30cd\u30c3\u30c8\u30de\u30b9\u30af", + "dhcp_form_range_title": "IP\u30a2\u30c9\u30ec\u30b9\u306e\u7bc4\u56f2", + "dhcp_form_range_start": "\u7bc4\u56f2\u306e\u958b\u59cb", + "dhcp_form_range_end": "\u7bc4\u56f2\u306e\u7d42\u4e86", + "dhcp_form_lease_title": "DHCP\u5272\u5f53\u6642\u9593\uff08\u79d2\u5358\u4f4d\uff09", + "dhcp_form_lease_input": "\u5272\u5f53\u671f\u9593", "back": "\u623b\u308b", "dashboard": "\u30c0\u30c3\u30b7\u30e5\u30dc\u30fc\u30c9", "settings": "\u8a2d\u5b9a", "filters": "\u30d5\u30a3\u30eb\u30bf", "query_log": "\u30af\u30a8\u30ea\u30fb\u30ed\u30b0", - "faq": "FAQ", + "faq": "\u3088\u304f\u3042\u308b\u8cea\u554f", "version": "\u30d0\u30fc\u30b8\u30e7\u30f3", "address": "\u30a2\u30c9\u30ec\u30b9", "on": "\u30aa\u30f3", @@ -16,18 +38,18 @@ "enabled_protection": "\u4fdd\u8b77\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f", "disable_protection": "\u4fdd\u8b77\u3092\u7121\u52b9\u306b\u3059\u308b", "disabled_protection": "\u4fdd\u8b77\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f", - "refresh_statics": "\u7d71\u8a08\u30c7\u30fc\u30bf\u3092\u66f4\u65b0\u3059\u308b", + "refresh_statics": "\u7d71\u8a08\u30c7\u30fc\u30bf\u3092\u6700\u65b0\u306b\u3059\u308b", "dns_query": "DNS\u30af\u30a8\u30ea", - "blocked_by": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30af\u30a8\u30ea\u30d5\u30a3\u30eb\u30bf", + "blocked_by": "\u30d5\u30a3\u30eb\u30bf\u306b\u3088\u308a\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30af\u30a8\u30ea", "stats_malware_phishing": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30de\u30eb\u30a6\u30a7\u30a2\uff0f\u30d5\u30a3\u30c3\u30b7\u30f3\u30b0", "stats_adult": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30a2\u30c0\u30eb\u30c8\u30a6\u30a7\u30d6\u30b5\u30a4\u30c8", "stats_query_domain": "\u6700\u3082\u554f\u5408\u305b\u3055\u308c\u305f\u30c9\u30e1\u30a4\u30f3", "for_last_24_hours": "\u904e\u53bb24\u6642\u9593\u4ee5\u5185", - "no_domains_found": "\u30c9\u30e1\u30a4\u30f3\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f", + "no_domains_found": "\u30c9\u30e1\u30a4\u30f3\u60c5\u5831\u306f\u3042\u308a\u307e\u305b\u3093", "requests_count": "\u30ea\u30af\u30a8\u30b9\u30c8\u6570", "top_blocked_domains": "\u6700\u3082\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30c9\u30e1\u30a4\u30f3", "top_clients": "\u30c8\u30c3\u30d7\u30af\u30e9\u30a4\u30a2\u30f3\u30c8", - "no_clients_found": "\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f", + "no_clients_found": "\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u60c5\u5831\u306f\u3042\u308a\u307e\u305b\u3093", "general_statistics": "\u5168\u822c\u7684\u306a\u7d71\u8a08", "number_of_dns_query_24_hours": "\u904e\u53bb24\u6642\u9593\u306b\u51e6\u7406\u3055\u308c\u305fDNS\u30af\u30a8\u30ea\u306e\u6570", "number_of_dns_query_blocked_24_hours": "\u5e83\u544a\u30d6\u30ed\u30c3\u30af\u30d5\u30a3\u30eb\u30bf\u3068hosts\u30d6\u30ed\u30c3\u30af\u30ea\u30b9\u30c8\u306b\u3088\u3063\u3066\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u6570", @@ -39,18 +61,18 @@ "average_processing_time_hint": "DNS\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u51e6\u7406\u306b\u304b\u304b\u308b\u5e73\u5747\u6642\u9593\uff08\u30df\u30ea\u79d2\u5358\u4f4d\uff09", "block_domain_use_filters_and_hosts": "\u30d5\u30a3\u30eb\u30bf\u3068hosts\u30d5\u30a1\u30a4\u30eb\u3092\u4f7f\u7528\u3057\u3066\u30c9\u30e1\u30a4\u30f3\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b", "filters_block_toggle_hint": "\u30d5\u30a3\u30eb\u30bf<\/a>\u306e\u8a2d\u5b9a\u3067\u30d6\u30ed\u30c3\u30af\u3059\u308b\u30eb\u30fc\u30eb\u3092\u8a2d\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002", - "use_adguard_browsing_sec": "AdGuard\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3Web\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b", - "use_adguard_browsing_sec_hint": "AdGuard Home\u306f\u3001\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3Web\u30b5\u30fc\u30d3\u30b9\u306b\u3088\u3063\u3066\u30c9\u30e1\u30a4\u30f3\u304c\u30d6\u30e9\u30c3\u30af\u30ea\u30b9\u30c8\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u3053\u308c\u306f\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u3092\u8003\u616e\u3057\u305fAPI\u3092\u4f7f\u7528\u3057\u3066\u30c1\u30a7\u30c3\u30af\u3092\u5b9f\u884c\u3057\u307e\u3059\u3002\u30c9\u30e1\u30a4\u30f3\u540dSHA256\u30cf\u30c3\u30b7\u30e5\u306e\u77ed\u3044\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u306e\u307f\u304c\u30b5\u30fc\u30d0\u30fc\u306b\u9001\u4fe1\u3055\u308c\u307e\u3059\u3002", - "use_adguard_parental": "AdGuard\u30da\u30a2\u30ec\u30f3\u30bf\u30eb\u30b3\u30f3\u30c8\u30ed\u30fc\u30ebWeb\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b", - "use_adguard_parental_hint": "AdGuard Home\u306f\u3001\u30c9\u30e1\u30a4\u30f3\u306b\u30a2\u30c0\u30eb\u30c8\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u542b\u307e\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3Web\u30b5\u30fc\u30d3\u30b9\u3068\u540c\u3058\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u306b\u512a\u3057\u3044API\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002", + "use_adguard_browsing_sec": "AdGuard\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b", + "use_adguard_browsing_sec_hint": "AdGuard Home\u306f\u3001\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u306b\u3088\u3063\u3066\u30c9\u30e1\u30a4\u30f3\u304c\u30d6\u30e9\u30c3\u30af\u30ea\u30b9\u30c8\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u3053\u308c\u306f\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u3092\u8003\u616e\u3057\u305fAPI\u3092\u4f7f\u7528\u3057\u3066\u30c1\u30a7\u30c3\u30af\u3092\u5b9f\u884c\u3057\u307e\u3059\u3002\u30c9\u30e1\u30a4\u30f3\u540dSHA256\u30cf\u30c3\u30b7\u30e5\u306e\u77ed\u3044\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u306e\u307f\u304c\u30b5\u30fc\u30d0\u306b\u9001\u4fe1\u3055\u308c\u307e\u3059\u3002", + "use_adguard_parental": "AdGuard\u30da\u30a2\u30ec\u30f3\u30bf\u30eb\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b", + "use_adguard_parental_hint": "AdGuard Home\u306f\u3001\u30c9\u30e1\u30a4\u30f3\u306b\u30a2\u30c0\u30eb\u30c8\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u542b\u307e\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u3068\u540c\u3058\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u306b\u512a\u3057\u3044API\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002", "enforce_safe_search": "\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1\u3092\u5f37\u5236\u3059\u308b", "enforce_save_search_hint": "AdGuard Home\u306f\u3001Google\u3001Youtube\u3001Bing\u3001Yandex\u306e\u691c\u7d22\u30a8\u30f3\u30b8\u30f3\u3067\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1\u3092\u5f37\u5236\u3067\u304d\u307e\u3059\u3002", - "no_servers_specified": "\u30b5\u30fc\u30d0\u30fc\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093", + "no_servers_specified": "\u30b5\u30fc\u30d0\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093", "no_settings": "\u8a2d\u5b9a\u306a\u3057", "general_settings": "\u4e00\u822c\u8a2d\u5b9a", - "upstream_dns": "\u30a2\u30c3\u30d7\u30b9\u30c8\u30ea\u30fc\u30e0DNS\u30b5\u30fc\u30d0\u30fc", - "upstream_dns_hint": "\u3053\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u672a\u5165\u529b\u306e\u307e\u307e\u306b\u3059\u308b\u3068\u3001AdGuard Home\u306fCloudflare DNS<\/a>\u3092\u30a2\u30c3\u30d7\u30b9\u30c8\u30ea\u30fc\u30e0\u3068\u3057\u3066\u4f7f\u7528\u3057\u307e\u3059\u3002DNS over TLS\u30b5\u30fc\u30d0\u30fc\u306b\u306f\u3001\uff62tls:\/\/\u300d\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u4f7f\u7528\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "test_upstream_btn": "\u30a2\u30c3\u30d7\u30b9\u30c8\u30ea\u30fc\u30e0\u30b5\u30fc\u30d0\u30fc\u3092\u30c6\u30b9\u30c8\u3059\u308b", + "upstream_dns": "\u4e0a\u6d41DNS\u30b5\u30fc\u30d0", + "upstream_dns_hint": "\u3053\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u672a\u5165\u529b\u306e\u307e\u307e\u306b\u3059\u308b\u3068\u3001AdGuard Home\u306f\u4e0a\u6d41\u3068\u3057\u3066Cloudflare DNS<\/a>\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002DNS over TLS\u30b5\u30fc\u30d0\u306b\u306f\u3001\uff62tls:\/\/\u300d\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u4f7f\u7528\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "test_upstream_btn": "\u4e0a\u6d41\u30b5\u30fc\u30d0\u3092\u30c6\u30b9\u30c8\u3059\u308b", "apply_btn": "\u9069\u7528\u3059\u308b", "disabled_filtering_toast": "\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f", "enabled_filtering_toast": "\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f", @@ -66,7 +88,7 @@ "rules_count_table_header": "\u30eb\u30fc\u30eb\u6570", "last_time_updated_table_header": "\u6700\u7d42\u66f4\u65b0\u6642\u523b", "actions_table_header": "\u64cd\u4f5c", - "delete_table_action": "\u524a\u9664", + "delete_table_action": "\u524a\u9664\u3059\u308b", "filters_and_hosts": "\u30d5\u30a3\u30eb\u30bf\u3068hosts\u30d6\u30ed\u30c3\u30af\u30ea\u30b9\u30c8", "filters_and_hosts_hint": "AdGuard Home\u306f\u3001\u57fa\u672c\u7684\u306a\u5e83\u544a\u30d6\u30ed\u30c3\u30af\u30eb\u30fc\u30eb\u3068hosts\u30d5\u30a1\u30a4\u30eb\u306e\u69cb\u6587\u3092\u7406\u89e3\u3057\u307e\u3059\u3002", "no_filters_added": "\u30d5\u30a3\u30eb\u30bf\u306f\u8ffd\u52a0\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f", @@ -82,17 +104,18 @@ "examples_title": "\u4f8b", "example_meaning_filter_block": "example.org\u30c9\u30e1\u30a4\u30f3\u3068\u305d\u306e\u3059\u3079\u3066\u306e\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3078\u306e\u30a2\u30af\u30bb\u30b9\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b", "example_meaning_filter_whitelist": "example.org\u30c9\u30e1\u30a4\u30f3\u3068\u305d\u306e\u3059\u3079\u3066\u306e\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3078\u306e\u30a2\u30af\u30bb\u30b9\u306e\u30d6\u30ed\u30c3\u30af\u3092\u89e3\u9664\u3059\u308b", - "example_meaning_host_block": "AdGuard Home\u306f\u3001example.org\u30c9\u30e1\u30a4\u30f3\uff08\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3092\u9664\u304f\uff09\u306b\u5bfe\u3057\u3066127.0.0.1\u306e\u30a2\u30c9\u30ec\u30b9\u3092\u8fd4\u3059\u3088\u3046\u306b\u306a\u308a\u307e\u3057\u305f\u3002", + "example_meaning_host_block": "AdGuard Home\u306f\u3001example.org\u30c9\u30e1\u30a4\u30f3\uff08\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3092\u9664\u304f\uff09\u306b\u5bfe\u3057\u3066127.0.0.1\u306e\u30a2\u30c9\u30ec\u30b9\u3092\u8fd4\u3059\u3088\u3046\u306b\u306a\u308a\u307e\u3059\u3002", "example_comment": "! \u3053\u3053\u306b\u306f\u30b3\u30e1\u30f3\u30c8\u304c\u5165\u308a\u307e\u3059", "example_comment_meaning": "\u305f\u3060\u306e\u30b3\u30e1\u30f3\u30c8\u3067\u3059", "example_comment_hash": "# \u3053\u3053\u3082\u30b3\u30e1\u30f3\u30c8\u3067\u3059", "example_upstream_regular": "\u901a\u5e38\u306eDNS\uff08UDP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09", "example_upstream_dot": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b DNS-over-TLS<\/a>", "example_upstream_doh": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b DNS-over-HTTPS<\/a>", + "example_upstream_sdns": "DNSCrypt<\/a> \u307e\u305f\u306f DNS-over-HTTPS<\/a> \u30ea\u30be\u30eb\u30d0\u306e\u305f\u3081\u306b DNS Stamps<\/a> \u3092\u4f7f\u3048\u307e\u3059", "example_upstream_tcp": "\u901a\u5e38\u306eDNS\uff08TCP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09", "all_filters_up_to_date_toast": "\u3059\u3079\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u306f\u65e2\u306b\u6700\u65b0\u3067\u3059", - "updated_upstream_dns_toast": "\u30a2\u30c3\u30d7\u30b9\u30c8\u30ea\u30fc\u30e0DNS\u30b5\u30fc\u30d0\u30fc\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f", - "dns_test_ok_toast": "\u6307\u5b9a\u3055\u308c\u305fDNS\u30b5\u30fc\u30d0\u30fc\u306f\u6b63\u3057\u304f\u52d5\u4f5c\u3057\u3066\u3044\u307e\u3059", + "updated_upstream_dns_toast": "\u4e0a\u6d41DNS\u30b5\u30fc\u30d0\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f", + "dns_test_ok_toast": "\u6307\u5b9a\u3055\u308c\u305fDNS\u30b5\u30fc\u30d0\u306f\u6b63\u3057\u304f\u52d5\u4f5c\u3057\u3066\u3044\u307e\u3059", "dns_test_not_ok_toast": "\u30b5\u30fc\u30d0 \"{{key}}\": \u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u6b63\u3057\u304f\u5165\u529b\u3055\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044", "unblock_btn": "\u30d6\u30ed\u30c3\u30af\u89e3\u9664", "block_btn": "\u30d6\u30ed\u30c3\u30af", @@ -104,7 +127,7 @@ "empty_response_status": "\u672a\u5b9a\u7fa9", "show_all_filter_type": "\u3059\u3079\u3066\u8868\u793a", "show_filtered_type": "\u30d5\u30a3\u30eb\u30bf\u3055\u308c\u305f\u30ed\u30b0\u3092\u8868\u793a", - "no_logs_found": "\u30ed\u30b0\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f", + "no_logs_found": "\u30ed\u30b0\u306f\u3042\u308a\u307e\u305b\u3093", "disabled_log_btn": "\u30ed\u30b0\u3092\u7121\u52b9\u306b\u3059\u308b", "download_log_file_btn": "\u30ed\u30b0\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b", "refresh_btn": "\u6700\u65b0\u306b\u3059\u308b", @@ -125,5 +148,6 @@ "found_in_known_domain_db": "\u65e2\u77e5\u306e\u30c9\u30e1\u30a4\u30f3\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002", "category_label": "\u30ab\u30c6\u30b4\u30ea", "rule_label": "\u30eb\u30fc\u30eb", - "filter_label": "\u30d5\u30a3\u30eb\u30bf" + "filter_label": "\u30d5\u30a3\u30eb\u30bf", + "unknown_filter": "\u4e0d\u660e\u306a\u30d5\u30a3\u30eb\u30bf {{filterId}}" } \ No newline at end of file diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json index 91705df6..1467b370 100644 --- a/client/src/__locales/pt-br.json +++ b/client/src/__locales/pt-br.json @@ -1,4 +1,26 @@ { + "refresh_status": "Atualizar status", + "save_config": "Salvar configura\u00e7\u00e3o", + "enabled_dhcp": "Servidor DHCP ativado", + "disabled_dhcp": "Servidor DHCP desativado", + "dhcp_title": "Servidor DHCP", + "dhcp_description": "Se o seu roteador n\u00e3o fornecer configura\u00e7\u00f5es de DHCP, voc\u00ea poder\u00e1 usar o servidor DHCP integrado do AdGuard.", + "dhcp_enable": "Ativar servidor DHCP", + "dhcp_disable": "Desativar servidor DHCP", + "dhcp_not_found": "Nenhum servidor DHCP ativo foi encontrado na sua rede. \u00c9 seguro ativar o servidor DHCP integrado.", + "dhcp_leases": "Concess\u00f5es DHCP", + "dhcp_leases_not_found": "Nenhuma concess\u00e3o DHCP encontrada", + "dhcp_config_saved": "Salvar configura\u00e7\u00f5es do servidor DHCP", + "form_error_required": "Campo obrigat\u00f3rio", + "form_error_ip_format": "formato de endere\u00e7o IPv4 inv\u00e1lido", + "form_error_positive": "Deve ser maior que 0", + "dhcp_form_gateway_input": "IP do gateway", + "dhcp_form_subnet_input": "M\u00e1scara de sub-rede", + "dhcp_form_range_title": "Faixa de endere\u00e7os IP", + "dhcp_form_range_start": "In\u00edcio da faixa", + "dhcp_form_range_end": "Final da faixa", + "dhcp_form_lease_title": "Tempo de concess\u00e3o do DHCP (em segundos)", + "dhcp_form_lease_input": "Dura\u00e7\u00e3o da concess\u00e3o", "back": "Voltar", "dashboard": "Painel", "settings": "Configura\u00e7\u00f5es", @@ -18,7 +40,7 @@ "disabled_protection": "Prote\u00e7\u00e3o desativada", "refresh_statics": "Atualizar estat\u00edsticas", "dns_query": "Consultas de DNS", - "blocked_by": "Bloqueador por Filtros", + "blocked_by": "Bloqueador por filtros", "stats_malware_phishing": "Bloqueado malware\/phishing", "stats_adult": "Bloqueado sites adultos", "stats_query_domain": "Principais dom\u00ednios consultados", @@ -89,6 +111,7 @@ "example_upstream_regular": "DNS regular (atrav\u00e9s do UDP)", "example_upstream_dot": "DNS criptografado atrav\u00e9s do TLS<\/a>", "example_upstream_doh": "DNS criptografado atrav\u00e9s do HTTPS<\/a>", + "example_upstream_sdns": "Voc\u00ea pode usar DNS Stamps<\/a>para oDNSCrypt<\/a>ou usar resolvedoresDNS-sobre-HTTPS<\/a>", "example_upstream_tcp": "DNS regular (atrav\u00e9s do TCP)", "all_filters_up_to_date_toast": "Todos os filtros j\u00e1 est\u00e3o atualizados", "updated_upstream_dns_toast": "Atualizado os servidores DNS upstream", @@ -125,5 +148,6 @@ "found_in_known_domain_db": "Encontrado no banco de dados de dom\u00ednios conhecidos.", "category_label": "Categoria", "rule_label": "Regra", - "filter_label": "Filtro" + "filter_label": "Filtro", + "unknown_filter": "Filtro desconhecido {{filterId}}" } \ No newline at end of file diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json index fb75e557..fdea033a 100644 --- a/client/src/__locales/sv.json +++ b/client/src/__locales/sv.json @@ -1,4 +1,26 @@ { + "refresh_status": "Uppdatera status", + "save_config": "Spara inst\u00e4llningar", + "enabled_dhcp": "DHCP-server aktiverad", + "disabled_dhcp": "Dhcp-server avaktiverad", + "dhcp_title": "DHCP-server", + "dhcp_description": "Om din router inte har inst\u00e4llningar f\u00f6r DHCP kan du anv\u00e4nda AdGuards inbyggda server.", + "dhcp_enable": "Aktivera DHCP.-server", + "dhcp_disable": "Avaktivera DHCP-server", + "dhcp_not_found": "Ingen aktiv DHCP-server hittades i n\u00e4tverkat.", + "dhcp_leases": "DHCP-lease", + "dhcp_leases_not_found": "Ingen DHCP-lease hittad", + "dhcp_config_saved": "Sparade inst\u00e4llningar f\u00f6r DHCP-servern", + "form_error_required": "Obligatoriskt f\u00e4lt", + "form_error_ip_format": "Ogiltigt IPv4-format", + "form_error_positive": "M\u00e5ste vara st\u00f6rre \u00e4n noll", + "dhcp_form_gateway_input": "Gateway-IP", + "dhcp_form_subnet_input": "Subnetmask", + "dhcp_form_range_title": "IP-adressgr\u00e4nser", + "dhcp_form_range_start": "Startgr\u00e4ns", + "dhcp_form_range_end": "Gr\u00e4nsslut", + "dhcp_form_lease_title": "DHCP-leasetid (i sekunder)", + "dhcp_form_lease_input": "Leasetid", "back": "Tiilbaka", "dashboard": "Kontrollpanel", "settings": "Inst\u00e4llningar", @@ -18,7 +40,7 @@ "disabled_protection": "Kopplade bort skydd", "refresh_statics": "Uppdatera statistik", "dns_query": "DNS-f\u00f6rfr\u00e5gningar", - "blocked_by": "Blockerat av Filter", + "blocked_by": "Blockerat av filter", "stats_malware_phishing": "Blockerad skadekod\/phising", "stats_adult": "Blockerade vuxensajter", "stats_query_domain": "Mest efters\u00f6kta dom\u00e4ner", @@ -89,6 +111,7 @@ "example_upstream_regular": "vanlig DNS (\u00f6ver UDP)", "example_upstream_dot": "krypterat DNS-over-TLS<\/a>", "example_upstream_doh": "krypterat DNS-over-HTTPS<\/a>", + "example_upstream_sdns": "Du kan anv\u00e4nda DNS-stamps<\/a> f\u00f6r DNSCrypt<\/a> eller DNS-\u00f6ver-HTTPS<\/a>\n-resolvers", "example_upstream_tcp": "vanlig DNS (\u00f6ver UDP)", "all_filters_up_to_date_toast": "Alla filter \u00e4r redan aktuella", "updated_upstream_dns_toast": "Uppdaterade uppstr\u00f6ms-dns-servrar", @@ -125,5 +148,6 @@ "found_in_known_domain_db": "Hittad i dom\u00e4ndatabas.", "category_label": "Kategori", "rule_label": "Regel", - "filter_label": "Filter" + "filter_label": "Filter", + "unknown_filter": "Ok\u00e4nt filter {{filterId}}" } \ No newline at end of file diff --git a/client/src/actions/index.js b/client/src/actions/index.js index bb9d9bbb..20ef3766 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -561,6 +561,7 @@ export const setDhcpConfig = config => async (dispatch) => { dispatch(setDhcpConfigRequest()); try { await apiClient.setDhcpConfig(config); + dispatch(getDhcpStatus()); dispatch(addSuccessToast('dhcp_config_saved')); dispatch(setDhcpConfigSuccess()); } catch (error) { @@ -576,8 +577,8 @@ export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE'); export const findActiveDhcp = () => async (dispatch) => { dispatch(findActiveDhcpRequest()); try { - await apiClient.findActiveDhcp(); - dispatch(findActiveDhcpSuccess()); + const activeDhcp = await apiClient.findActiveDhcp(); + dispatch(findActiveDhcpSuccess(activeDhcp)); } catch (error) { dispatch(addErrorToast({ error })); dispatch(findActiveDhcpFailure()); diff --git a/client/src/components/Settings/Dhcp/Form.js b/client/src/components/Settings/Dhcp/Form.js index 93140580..14455b1b 100644 --- a/client/src/components/Settings/Dhcp/Form.js +++ b/client/src/components/Settings/Dhcp/Form.js @@ -49,16 +49,19 @@ const renderInterfaces = (interfaces => ( Object.keys(interfaces).map((item) => { const option = interfaces[item]; const { name } = option; + const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':')); let interfaceIP = option.ip_addresses[0]; - option.ip_addresses.forEach((ip) => { - if (!ip.includes(':')) { - interfaceIP = ip; - } - }); + if (!onlyIPv6) { + option.ip_addresses.forEach((ip) => { + if (!ip.includes(':')) { + interfaceIP = ip; + } + }); + } return ( - ); @@ -91,7 +94,6 @@ let Form = (props) => { handleSubmit, pristine, submitting, - enabled, interfaces, processing, interfaceValue, @@ -106,7 +108,11 @@ let Form = (props) => {
- + {renderInterfaces(interfaces)} @@ -131,7 +137,6 @@ let Form = (props) => { className="form-control" placeholder={t('dhcp_form_gateway_input')} validate={[ipv4, required]} - disabled={!enabled} />
@@ -143,7 +148,6 @@ let Form = (props) => { className="form-control" placeholder={t('dhcp_form_subnet_input')} validate={[ipv4, required]} - disabled={!enabled} />
@@ -161,7 +165,6 @@ let Form = (props) => { className="form-control" placeholder={t('dhcp_form_range_start')} validate={[ipv4, required]} - disabled={!enabled} />
@@ -172,7 +175,6 @@ let Form = (props) => { className="form-control" placeholder={t('dhcp_form_range_end')} validate={[ipv4, required]} - disabled={!enabled} />
@@ -186,7 +188,6 @@ let Form = (props) => { className="form-control" placeholder={t('dhcp_form_lease_input')} validate={[required, isPositive]} - disabled={!enabled} normalize={toNumber} />
@@ -196,7 +197,7 @@ let Form = (props) => { @@ -208,10 +209,10 @@ Form.propTypes = { handleSubmit: PropTypes.func, pristine: PropTypes.bool, submitting: PropTypes.bool, - enabled: PropTypes.bool, interfaces: PropTypes.object, processing: PropTypes.bool, interfaceValue: PropTypes.string, + initialValues: PropTypes.object, t: PropTypes.func, }; diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js index e0317cd7..d18a9b93 100644 --- a/client/src/components/Settings/Dhcp/index.js +++ b/client/src/components/Settings/Dhcp/index.js @@ -22,7 +22,12 @@ class Dhcp extends Component { const buttonClass = config.enabled ? 'btn-gray' : 'btn-success'; return ( - ); @@ -41,22 +46,6 @@ class Dhcp extends Component {
-
- {this.getToggleDhcpButton()} - -
- {dhcp.active && !dhcp.active.found && -
- dhcp_not_found -
- } -
+
+
+ {this.getToggleDhcpButton()} + +
+ {dhcp.active && +
+ {dhcp.active.found ? ( + + dhcp_found + + ) : ( + dhcp_not_found + )} +
+ }
From 6fd9af3c6033d4189e3c48a1a4698981a8183024 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 28 Dec 2018 20:49:27 +0300 Subject: [PATCH 24/45] =?UTF-8?q?/dhcp/set=5Fconfig=20API=20=E2=80=94=20do?= =?UTF-8?q?n't=20forget=20to=20save=20changed=20config=20to=20YAML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dhcp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dhcp.go b/dhcp.go index b86fa29c..738f8dad 100644 --- a/dhcp.go +++ b/dhcp.go @@ -43,6 +43,7 @@ func handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { dhcpServer.Stop() } config.DHCP = newconfig + httpUpdateConfigReloadDNSReturnOK(w, r) } func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { From f5e7eed44762f1258f54b7c641b025087230b966 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 28 Dec 2018 20:50:00 +0300 Subject: [PATCH 25/45] =?UTF-8?q?/dhcp/find=5Factive=5Fdhcp=20API=20?= =?UTF-8?q?=E2=80=94=20Don't=20return=20'found'=20key=20when=20there's=20a?= =?UTF-8?q?n=20error.=20And=20return=20error=20string.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dhcp.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dhcp.go b/dhcp.go index 738f8dad..d025b6b5 100644 --- a/dhcp.go +++ b/dhcp.go @@ -110,12 +110,11 @@ func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { // implement func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { found, err := dhcpd.CheckIfOtherDHCPServersPresent(config.DHCP.InterfaceName) - result := map[string]interface{}{ - "found": found, - } + result := map[string]interface{}{} if err != nil { - result["found"] = false - result["error"] = err + result["error"] = err.Error() + } else { + result["found"] = found } w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(result) From 1b7db4906275f603f27a54a5700afd1f523cee22 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 28 Dec 2018 20:50:24 +0300 Subject: [PATCH 26/45] =?UTF-8?q?DHCPD=20=E2=80=94=20don't=20forget=20to?= =?UTF-8?q?=20make=20Lease=20fields=20public.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dhcpd/dhcpd.go | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 8dc500a1..74f6c2fb 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -14,9 +14,9 @@ const defaultDiscoverTime = time.Second * 3 // field ordering is important -- yaml fields will mirror ordering from here type Lease struct { - hwaddr net.HardwareAddr `json:"mac" yaml:"hwaddr"` - ip net.IP `json:"ip"` - expiry time.Time `json:"expires"` + HWAddr net.HardwareAddr `json:"mac" yaml:"hwaddr"` + IP net.IP `json:"ip"` + Expiry time.Time `json:"expires"` } // field ordering is important -- yaml fields will mirror ordering from here @@ -176,7 +176,7 @@ func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) { return nil, wrapErrPrint(err, "Couldn't find free IP for the lease %s", hwaddr.String()) } trace("Assigning to %s IP address %s", hwaddr, ip.String()) - lease := &Lease{hwaddr: hwaddr, ip: ip} + lease := &Lease{HWAddr: hwaddr, IP: ip} s.leases = append(s.leases, lease) return lease, nil } @@ -184,7 +184,7 @@ func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) { func (s *Server) locateLease(p dhcp4.Packet) *Lease { hwaddr := p.CHAddr() for i := range s.leases { - if bytes.Equal([]byte(hwaddr), []byte(s.leases[i].hwaddr)) { + if bytes.Equal([]byte(hwaddr), []byte(s.leases[i].HWAddr)) { // trace("bytes.Equal(%s, %s) returned true", hwaddr, s.leases[i].hwaddr) return s.leases[i] } @@ -247,7 +247,7 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh trace("Got %v message", msgType) trace("Leases:") for i, lease := range s.leases { - trace("Lease #%d: hwaddr %s, ip %s, expiry %s", i, lease.hwaddr, lease.ip, lease.expiry) + trace("Lease #%d: hwaddr %s, ip %s, expiry %s", i, lease.HWAddr, lease.IP, lease.Expiry) } trace("IP pool:") for ip, hwaddr := range s.IPpool { @@ -285,8 +285,8 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh // couldn't find lease, don't respond return nil } - reply := dhcp4.ReplyPacket(p, dhcp4.Offer, s.ipnet.IP, lease.ip, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) - trace("Replying with offer: offered IP %v for %v with options %+v", lease.ip, s.leaseTime, reply.ParseOptions()) + reply := dhcp4.ReplyPacket(p, dhcp4.Offer, s.ipnet.IP, lease.IP, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) + trace("Replying with offer: offered IP %v for %v with options %+v", lease.IP, s.leaseTime, reply.ParseOptions()) return reply case dhcp4.Request: // Broadcast From Client - I'll take that IP (Also start for renewals) // start/renew a lease -- update lease time @@ -320,35 +320,35 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh return nil } - if lease.ip.Equal(reqIP) { + if lease.IP.Equal(reqIP) { // IP matches lease IP, nothing else to do - lease.expiry = time.Now().Add(s.leaseTime) - trace("Replying with ACK: request IP matches lease IP, nothing else to do. IP %v for %v", lease.ip, p.CHAddr()) - return dhcp4.ReplyPacket(p, dhcp4.ACK, s.ipnet.IP, lease.ip, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) + lease.Expiry = time.Now().Add(s.leaseTime) + trace("Replying with ACK: request IP matches lease IP, nothing else to do. IP %v for %v", lease.IP, p.CHAddr()) + return dhcp4.ReplyPacket(p, dhcp4.ACK, s.ipnet.IP, lease.IP, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) } // // requested IP different from lease // - trace("lease IP is different from requested IP: %s vs %s", lease.ip, reqIP) + trace("lease IP is different from requested IP: %s vs %s", lease.IP, reqIP) hwaddr := s.getIPpool(reqIP) if hwaddr == nil { // not in pool, check if it's in DHCP range if dhcp4.IPInRange(s.leaseStart, s.leaseStop, reqIP) { // okay, we can give it to our client -- it's in our DHCP range and not taken, so let them use their IP - trace("Replying with ACK: request IP %v is not taken, so assigning lease IP %v to it, for %v", reqIP, lease.ip, p.CHAddr()) - s.unreserveIP(lease.ip) - lease.ip = reqIP + trace("Replying with ACK: request IP %v is not taken, so assigning lease IP %v to it, for %v", reqIP, lease.IP, p.CHAddr()) + s.unreserveIP(lease.IP) + lease.IP = reqIP s.reserveIP(reqIP, p.CHAddr()) - lease.expiry = time.Now().Add(s.leaseTime) - return dhcp4.ReplyPacket(p, dhcp4.ACK, s.ipnet.IP, lease.ip, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) + lease.Expiry = time.Now().Add(s.leaseTime) + return dhcp4.ReplyPacket(p, dhcp4.ACK, s.ipnet.IP, lease.IP, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) } } - if hwaddr != nil && !bytes.Equal(hwaddr, lease.hwaddr) { - log.Printf("SHOULD NOT HAPPEN: IP pool hwaddr does not match lease hwaddr: %s vs %s", hwaddr, lease.hwaddr) + if hwaddr != nil && !bytes.Equal(hwaddr, lease.HWAddr) { + log.Printf("SHOULD NOT HAPPEN: IP pool hwaddr does not match lease hwaddr: %s vs %s", hwaddr, lease.HWAddr) } // requsted IP is not sufficient, reply with NAK From f868fdbf7a5ee165e32d8a16e2a151e62afdcfc9 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 28 Dec 2018 21:00:41 +0300 Subject: [PATCH 27/45] Update makefile to detect changes in dhcpd/*.go --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9e9ae505..9cc18d7a 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ client/node_modules: client/package.json client/package-lock.json $(STATIC): $(JSFILES) client/node_modules npm --prefix client run build-prod -$(TARGET): $(STATIC) *.go dnsfilter/*.go dnsforward/*.go +$(TARGET): $(STATIC) *.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go go get -d . GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) GO111MODULE=off go get -v github.com/gobuffalo/packr/... PATH=$(GOPATH)/bin:$(PATH) packr -z From 03effab345e2bc9e77926a64acd426f71b5004cd Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 28 Dec 2018 21:01:16 +0300 Subject: [PATCH 28/45] Start DHCP on launch if it's enabled in config. --- app.go | 5 +++++ dhcp.go | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index bbe36359..0bfb6e1b 100644 --- a/app.go +++ b/app.go @@ -192,6 +192,11 @@ func main() { log.Fatal(err) } + err = startDHCPServer() + if err != nil { + log.Fatal(err) + } + URL := fmt.Sprintf("http://%s", address) log.Println("Go to " + URL) log.Fatal(http.ListenAndServe(address, nil)) diff --git a/dhcp.go b/dhcp.go index d025b6b5..66be4714 100644 --- a/dhcp.go +++ b/dhcp.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/AdguardTeam/AdGuardHome/dhcpd" + "github.com/joomcode/errorx" ) var dhcpServer = dhcpd.Server{} @@ -107,7 +108,6 @@ func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { } } -// implement func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { found, err := dhcpd.CheckIfOtherDHCPServersPresent(config.DHCP.InterfaceName) result := map[string]interface{}{} @@ -123,3 +123,11 @@ func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { return } } + +func startDHCPServer() error { + err := dhcpServer.Start(&config.DHCP) + if err != nil { + return errorx.Decorate(err, "Couldn't start DHCP server") + } + return nil +} From 8fc5aebf1265b343fe76cdaf1973ac5a3784b498 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Fri, 28 Dec 2018 21:01:31 +0300 Subject: [PATCH 29/45] Pretty-format leases so it shows human readable MAC address. --- dhcp.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dhcp.go b/dhcp.go index 66be4714..26bfa4a2 100644 --- a/dhcp.go +++ b/dhcp.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net" "net/http" + "time" "github.com/AdguardTeam/AdGuardHome/dhcpd" "github.com/joomcode/errorx" @@ -12,9 +13,20 @@ import ( var dhcpServer = dhcpd.Server{} func handleDHCPStatus(w http.ResponseWriter, r *http.Request) { + rawLeases := dhcpServer.Leases() + leases := []map[string]string{} + for i := range rawLeases { + lease := map[string]string{ + "mac": rawLeases[i].HWAddr.String(), + "ip": rawLeases[i].IP.String(), + "expires": rawLeases[i].Expiry.Format(time.RFC3339), + } + leases = append(leases, lease) + + } status := map[string]interface{}{ "config": config.DHCP, - "leases": dhcpServer.Leases(), + "leases": leases, } w.Header().Set("Content-Type", "application/json") From f312575da40d7cd332c7300d09cde867af264402 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Sat, 29 Dec 2018 00:49:39 +0300 Subject: [PATCH 30/45] verbose output parameter --- app.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 0bfb6e1b..89605843 100644 --- a/app.go +++ b/app.go @@ -3,7 +3,6 @@ package main import ( "bufio" "fmt" - "log" "net" "net/http" "os" @@ -14,6 +13,7 @@ import ( "time" "github.com/gobuffalo/packr" + log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" ) @@ -62,6 +62,7 @@ func main() { } bindPort = &v }}, + {"verbose", "v", "enable verbose output", nil}, {"help", "h", "print this help", nil}, } printHelp := func() { @@ -79,6 +80,9 @@ func main() { printHelp() os.Exit(64) } + if v == "--verbose" || v == "-v" { + log.SetLevel(log.TraceLevel) + } knownParam := false for _, opt := range opts { if v == "--"+opt.longName { From a7e0f664921f8ac7de59d770c26e390e389b009e Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 14:54:45 +0300 Subject: [PATCH 31/45] Flag parser -- support options without values, move code for help and verbose into table. --- app.go | 56 ++++++++++++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/app.go b/app.go index 89605843..8cb86054 100644 --- a/app.go +++ b/app.go @@ -44,28 +44,30 @@ func main() { // config can be specified, which reads options from there, but other command line flags have to override config values // therefore, we must do it manually instead of using a lib { + var printHelp func() var configFilename *string var bindHost *string var bindPort *int var opts = []struct { - longName string - shortName string - description string - callback func(value string) + longName string + shortName string + description string + callbackWithValue func(value string) + callbackNoValue func() }{ - {"config", "c", "path to config file", func(value string) { configFilename = &value }}, - {"host", "h", "host address to bind HTTP server on", func(value string) { bindHost = &value }}, + {"config", "c", "path to config file", func(value string) { configFilename = &value }, nil}, + {"host", "h", "host address to bind HTTP server on", func(value string) { bindHost = &value }, nil}, {"port", "p", "port to serve HTTP pages on", func(value string) { v, err := strconv.Atoi(value) if err != nil { panic("Got port that is not a number") } bindPort = &v - }}, - {"verbose", "v", "enable verbose output", nil}, - {"help", "h", "print this help", nil}, + }, nil}, + {"verbose", "v", "enable verbose output", nil, func() { log.SetLevel(log.TraceLevel) }}, + {"help", "h", "print this help", nil, func() { printHelp(); os.Exit(64) }}, } - printHelp := func() { + printHelp = func() { fmt.Printf("Usage:\n\n") fmt.Printf("%s [options]\n\n", os.Args[0]) fmt.Printf("Options:\n") @@ -75,33 +77,19 @@ func main() { } for i := 1; i < len(os.Args); i++ { v := os.Args[i] - // short-circuit for help - if v == "--help" || v == "-h" { - printHelp() - os.Exit(64) - } - if v == "--verbose" || v == "-v" { - log.SetLevel(log.TraceLevel) - } knownParam := false for _, opt := range opts { - if v == "--"+opt.longName { - if i+1 > len(os.Args) { - log.Printf("ERROR: Got %s without argument\n", v) - os.Exit(64) + if v == "--"+opt.longName || v == "-"+opt.shortName { + if opt.callbackWithValue != nil { + if i+1 > len(os.Args) { + log.Printf("ERROR: Got %s without argument\n", v) + os.Exit(64) + } + i++ + opt.callbackWithValue(os.Args[i]) + } else if opt.callbackNoValue != nil { + opt.callbackNoValue() } - i++ - opt.callback(os.Args[i]) - knownParam = true - break - } - if v == "-"+opt.shortName { - if i+1 > len(os.Args) { - log.Printf("ERROR: Got %s without argument\n", v) - os.Exit(64) - } - i++ - opt.callback(os.Args[i]) knownParam = true break } From 55a7ff7447e328bd75b816c90c902c36bf723fa0 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 14:55:35 +0300 Subject: [PATCH 32/45] Get rid of logrus, it's TTY output is not friendly or human parseable if we will want users to send us logs. --- app.go | 4 ++-- dnsforward/dnsforward.go | 6 +++--- dnsforward/querylog.go | 2 +- dnsforward/querylog_file.go | 3 +-- dnsforward/querylog_top.go | 3 +-- dnsforward/stats.go | 3 +-- go.mod | 1 - 7 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app.go b/app.go index 8cb86054..285018eb 100644 --- a/app.go +++ b/app.go @@ -3,6 +3,7 @@ package main import ( "bufio" "fmt" + "log" "net" "net/http" "os" @@ -13,7 +14,6 @@ import ( "time" "github.com/gobuffalo/packr" - log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" ) @@ -64,7 +64,7 @@ func main() { } bindPort = &v }, nil}, - {"verbose", "v", "enable verbose output", nil, func() { log.SetLevel(log.TraceLevel) }}, + // {"verbose", "v", "enable verbose output", nil, func() { log.SetLevel(log.TraceLevel) }}, {"help", "h", "print this help", nil, func() { printHelp(); os.Exit(64) }}, } printHelp = func() { diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index 34f64694..a814f660 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -3,6 +3,7 @@ package dnsforward import ( "errors" "fmt" + "log" "net" "strings" "sync" @@ -13,7 +14,6 @@ import ( "github.com/AdguardTeam/dnsproxy/upstream" "github.com/joomcode/errorx" "github.com/miekg/dns" - log "github.com/sirupsen/logrus" ) // DefaultTimeout is the default upstream timeout @@ -283,7 +283,7 @@ func (s *Server) filterDNSRequest(d *proxy.DNSContext) (*dnsfilter.Result, error // Return immediately if there's an error return nil, errorx.Decorate(err, "dnsfilter failed to check host '%s'", host) } else if res.IsFiltered { - log.Debugf("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule) + // trace("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule) d.Res = s.genDNSFilterMessage(d, &res) } @@ -324,7 +324,7 @@ func (s *Server) genARecord(request *dns.Msg, ip net.IP) *dns.Msg { resp.SetReply(request) answer, err := dns.NewRR(fmt.Sprintf("%s %d A %s", request.Question[0].Name, s.BlockedResponseTTL, ip.String())) if err != nil { - log.Warnf("Couldn't generate A record for up replacement host '%s': %s", ip.String(), err) + log.Printf("Couldn't generate A record for replacement host '%s': %s", ip.String(), err) return s.genServerFailure(request) } resp.Answer = append(resp.Answer, answer) diff --git a/dnsforward/querylog.go b/dnsforward/querylog.go index 51ca3575..de740d1a 100644 --- a/dnsforward/querylog.go +++ b/dnsforward/querylog.go @@ -3,6 +3,7 @@ package dnsforward import ( "encoding/json" "fmt" + "log" "net" "net/http" "strconv" @@ -12,7 +13,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/dnsfilter" "github.com/miekg/dns" - log "github.com/sirupsen/logrus" ) const ( diff --git a/dnsforward/querylog_file.go b/dnsforward/querylog_file.go index 43a93093..19097baa 100644 --- a/dnsforward/querylog_file.go +++ b/dnsforward/querylog_file.go @@ -5,12 +5,11 @@ import ( "compress/gzip" "encoding/json" "fmt" + "log" "os" "sync" "time" - log "github.com/sirupsen/logrus" - "github.com/go-test/deep" ) diff --git a/dnsforward/querylog_top.go b/dnsforward/querylog_top.go index 26c896fa..b78dea79 100644 --- a/dnsforward/querylog_top.go +++ b/dnsforward/querylog_top.go @@ -3,6 +3,7 @@ package dnsforward import ( "bytes" "fmt" + "log" "net/http" "os" "path" @@ -13,8 +14,6 @@ import ( "sync" "time" - log "github.com/sirupsen/logrus" - "github.com/bluele/gcache" "github.com/miekg/dns" ) diff --git a/dnsforward/stats.go b/dnsforward/stats.go index 2befcad2..9cfe5f58 100644 --- a/dnsforward/stats.go +++ b/dnsforward/stats.go @@ -3,12 +3,11 @@ package dnsforward import ( "encoding/json" "fmt" + "log" "net/http" "sync" "time" - log "github.com/sirupsen/logrus" - "github.com/AdguardTeam/AdGuardHome/dnsfilter" ) diff --git a/go.mod b/go.mod index bdc4c0d3..41a97633 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,6 @@ require ( github.com/pkg/errors v0.8.0 github.com/shirou/gopsutil v2.18.10+incompatible github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect - github.com/sirupsen/logrus v1.2.0 go.uber.org/goleak v0.10.0 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 golang.org/x/net v0.0.0-20181220203305-927f97764cc3 From 7ab03e933545f2939d1a3bf32e4ee153f176eba6 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 15:04:14 +0300 Subject: [PATCH 33/45] Don't try to start DHCP server if it's not enabled. --- dhcp.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dhcp.go b/dhcp.go index 26bfa4a2..33d27467 100644 --- a/dhcp.go +++ b/dhcp.go @@ -137,6 +137,10 @@ func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { } func startDHCPServer() error { + if config.DHCP.Enabled == false { + // not enabled, don't do anything + return nil + } err := dhcpServer.Start(&config.DHCP) if err != nil { return errorx.Decorate(err, "Couldn't start DHCP server") From 4efa30edc4bb20ea52191be09d3d83f789a880e8 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 16:39:29 +0300 Subject: [PATCH 34/45] /dhcp/find_active_dhcp -- use interface name from request body --- dhcp.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/dhcp.go b/dhcp.go index 33d27467..890ba6b4 100644 --- a/dhcp.go +++ b/dhcp.go @@ -121,7 +121,22 @@ func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { } func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { - found, err := dhcpd.CheckIfOtherDHCPServersPresent(config.DHCP.InterfaceName) + body, err := ioutil.ReadAll(r.Body) + if err != nil { + errorText := fmt.Sprintf("failed to read request body: %s", err) + log.Println(errorText) + http.Error(w, errorText, http.StatusBadRequest) + return + } + + interfaceName := strings.TrimSpace(string(body)) + if interfaceName == "" { + errorText := fmt.Sprintf("empty interface name specified") + log.Println(errorText) + http.Error(w, errorText, http.StatusBadRequest) + return + } + found, err := dhcpd.CheckIfOtherDHCPServersPresent(interfaceName) result := map[string]interface{}{} if err != nil { result["error"] = err.Error() From bf15a40248a7f400a20feda9699398265241e43d Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 16:40:29 +0300 Subject: [PATCH 35/45] Fixup of previous commit. --- dhcp.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dhcp.go b/dhcp.go index 890ba6b4..4d6d257a 100644 --- a/dhcp.go +++ b/dhcp.go @@ -2,8 +2,12 @@ package main import ( "encoding/json" + "fmt" + "io/ioutil" + "log" "net" "net/http" + "strings" "time" "github.com/AdguardTeam/AdGuardHome/dhcpd" From fedfc3a1fd4e4592579f73b1126d532a2070e01a Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 16:40:38 +0300 Subject: [PATCH 36/45] Update comment why filter_conn.go is needed. --- dhcpd/filter_conn.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dhcpd/filter_conn.go b/dhcpd/filter_conn.go index cd943ab1..ec34c795 100644 --- a/dhcpd/filter_conn.go +++ b/dhcpd/filter_conn.go @@ -7,13 +7,15 @@ import ( "golang.org/x/net/ipv4" ) +// filterConn listens to 0.0.0.0:67, but accepts packets only from specific interface +// This is neccessary for DHCP daemon to work, since binding to IP address doesn't +// us access to see Discover/Request packets from clients. +// // TODO: on windows, controlmessage does not work, try to find out another way // https://github.com/golang/net/blob/master/ipv4/payload.go#L13 - type filterConn struct { iface net.Interface conn *ipv4.PacketConn - // cm *ipv4.ControlMessage } func newFilterConn(iface net.Interface, address string) (*filterConn, error) { From 86d79ae232f033312a251c84ce232a8b24e7ee76 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 16:44:07 +0300 Subject: [PATCH 37/45] dhcpd -- Remember hostname, for UI. --- dhcpd/dhcpd.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 74f6c2fb..1503749d 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -14,9 +14,10 @@ const defaultDiscoverTime = time.Second * 3 // field ordering is important -- yaml fields will mirror ordering from here type Lease struct { - HWAddr net.HardwareAddr `json:"mac" yaml:"hwaddr"` - IP net.IP `json:"ip"` - Expiry time.Time `json:"expires"` + HWAddr net.HardwareAddr `json:"mac" yaml:"hwaddr"` + IP net.IP `json:"ip"` + Hostname string `json:"hostname"` + Expiry time.Time `json:"expires"` } // field ordering is important -- yaml fields will mirror ordering from here @@ -176,7 +177,8 @@ func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) { return nil, wrapErrPrint(err, "Couldn't find free IP for the lease %s", hwaddr.String()) } trace("Assigning to %s IP address %s", hwaddr, ip.String()) - lease := &Lease{HWAddr: hwaddr, IP: ip} + hostname := p.ParseOptions()[dhcp4.OptionHostName] + lease := &Lease{HWAddr: hwaddr, IP: ip, Hostname: string(hostname)} s.leases = append(s.leases, lease) return lease, nil } From 7acb107cbfb0d458a3e0430fb3b2382a836ab05e Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 17:04:40 +0300 Subject: [PATCH 38/45] /dhcp/status -- give out hostname for UI --- dhcp.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dhcp.go b/dhcp.go index 4d6d257a..33355d8c 100644 --- a/dhcp.go +++ b/dhcp.go @@ -21,9 +21,10 @@ func handleDHCPStatus(w http.ResponseWriter, r *http.Request) { leases := []map[string]string{} for i := range rawLeases { lease := map[string]string{ - "mac": rawLeases[i].HWAddr.String(), - "ip": rawLeases[i].IP.String(), - "expires": rawLeases[i].Expiry.Format(time.RFC3339), + "mac": rawLeases[i].HWAddr.String(), + "ip": rawLeases[i].IP.String(), + "hostname": rawLeases[i].Hostname, + "expires": rawLeases[i].Expiry.Format(time.RFC3339), } leases = append(leases, lease) From 7463e54258c1b8a0ee6b8557081d2e00ef81b826 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 17:23:08 +0300 Subject: [PATCH 39/45] Implement a log wrapper --- log/log.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 log/log.go diff --git a/log/log.go b/log/log.go new file mode 100644 index 00000000..db6132e7 --- /dev/null +++ b/log/log.go @@ -0,0 +1,35 @@ +// Wrapper for standard library log, with the only difference is that it has extra function Tracef() and optional verbose flag to enable output from that. +package log + +import ( + "fmt" + "log" + "os" + "path" + "runtime" + "strings" +) + +var Verbose = false + +// Printf calls Output to print to the standard logger. +// Arguments are handled in the manner of fmt.Printf. +func Printf(format string, v ...interface{}) { + log.Printf(format, v...) +} + +func Tracef(format string, v ...interface{}) { + if Verbose { + pc := make([]uintptr, 10) // at least 1 entry needed + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + var buf strings.Builder + buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name()))) + text := fmt.Sprintf(format, v...) + buf.WriteString(text) + if len(text) == 0 || text[len(text)-1] != '\n' { + buf.WriteRune('\n') + } + fmt.Fprint(os.Stderr, buf.String()) + } +} From d8802a9709733ed1e1d34320e0c7048ac18e4ad8 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 17:23:42 +0300 Subject: [PATCH 40/45] Use new log wrapper and add more functions to it. --- app.go | 4 +-- config.go | 2 +- control.go | 6 ++-- dhcp.go | 2 +- dhcpd/check_other_dhcp.go | 5 ++-- dhcpd/dhcpd.go | 60 ++++++++++++++++++------------------- dhcpd/helpers.go | 19 +----------- dhcpd/standalone/main.go | 2 +- dns.go | 2 +- dnsfilter/dnsfilter.go | 2 +- dnsfilter/dnsfilter_test.go | 31 +++++++++---------- dnsfilter/helpers.go | 18 ----------- dnsforward/dnsforward.go | 4 +-- dnsforward/querylog.go | 2 +- dnsforward/querylog_file.go | 4 +-- dnsforward/querylog_top.go | 2 +- dnsforward/stats.go | 10 +++---- filter.go | 2 +- helpers.go | 15 ---------- i18n.go | 3 +- log/log.go | 22 ++++++++++++++ upgrade.go | 2 +- 22 files changed, 96 insertions(+), 123 deletions(-) diff --git a/app.go b/app.go index 285018eb..4d5e53a9 100644 --- a/app.go +++ b/app.go @@ -3,7 +3,6 @@ package main import ( "bufio" "fmt" - "log" "net" "net/http" "os" @@ -13,6 +12,7 @@ import ( "syscall" "time" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/gobuffalo/packr" "golang.org/x/crypto/ssh/terminal" ) @@ -64,7 +64,7 @@ func main() { } bindPort = &v }, nil}, - // {"verbose", "v", "enable verbose output", nil, func() { log.SetLevel(log.TraceLevel) }}, + {"verbose", "v", "enable verbose output", nil, func() { log.Verbose = true }}, {"help", "h", "print this help", nil, func() { printHelp(); os.Exit(64) }}, } printHelp = func() { diff --git a/config.go b/config.go index 15ecffc1..8fc7255d 100644 --- a/config.go +++ b/config.go @@ -2,7 +2,6 @@ package main import ( "io/ioutil" - "log" "os" "path/filepath" "sync" @@ -10,6 +9,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/dhcpd" "github.com/AdguardTeam/AdGuardHome/dnsfilter" "github.com/AdguardTeam/AdGuardHome/dnsforward" + "github.com/AdguardTeam/AdGuardHome/log" "gopkg.in/yaml.v2" ) diff --git a/control.go b/control.go index 2671461d..b15e2a25 100644 --- a/control.go +++ b/control.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net" "net/http" "os" @@ -12,11 +11,10 @@ import ( "strings" "time" - "github.com/AdguardTeam/dnsproxy/upstream" - "github.com/AdguardTeam/AdGuardHome/dnsforward" + "github.com/AdguardTeam/AdGuardHome/log" + "github.com/AdguardTeam/dnsproxy/upstream" "github.com/miekg/dns" - "gopkg.in/asaskevich/govalidator.v4" ) diff --git a/dhcp.go b/dhcp.go index 33355d8c..983f755a 100644 --- a/dhcp.go +++ b/dhcp.go @@ -4,13 +4,13 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net" "net/http" "strings" "time" "github.com/AdguardTeam/AdGuardHome/dhcpd" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/joomcode/errorx" ) diff --git a/dhcpd/check_other_dhcp.go b/dhcpd/check_other_dhcp.go index 7aed85ce..5d81953c 100644 --- a/dhcpd/check_other_dhcp.go +++ b/dhcpd/check_other_dhcp.go @@ -9,6 +9,7 @@ import ( "os" "time" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/krolaw/dhcp4" ) @@ -85,7 +86,7 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) { } // bind to 0.0.0.0:68 - trace("Listening to udp4 %+v", udpAddr) + log.Tracef("Listening to udp4 %+v", udpAddr) c, err := net.ListenPacket("udp4", src) if c != nil { defer c.Close() @@ -104,7 +105,7 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) { } // wait for answer - trace("Waiting %v for an answer", defaultDiscoverTime) + log.Tracef("Waiting %v for an answer", defaultDiscoverTime) // TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts b := make([]byte, 1500) c.SetReadDeadline(time.Now().Add(defaultDiscoverTime)) diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 1503749d..d663121a 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -3,10 +3,10 @@ package dhcpd import ( "bytes" "fmt" - "log" "net" "time" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/krolaw/dhcp4" ) @@ -167,16 +167,16 @@ func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) { copy(hwaddr, hwaddrCOW) foundLease := s.locateLease(p) if foundLease != nil { - // trace("found lease for %s: %+v", hwaddr, foundLease) + // log.Tracef("found lease for %s: %+v", hwaddr, foundLease) return foundLease, nil } // not assigned a lease, create new one, find IP from LRU - trace("Lease not found for %s: creating new one", hwaddr) + log.Tracef("Lease not found for %s: creating new one", hwaddr) ip, err := s.findFreeIP(p, hwaddr) if err != nil { return nil, wrapErrPrint(err, "Couldn't find free IP for the lease %s", hwaddr.String()) } - trace("Assigning to %s IP address %s", hwaddr, ip.String()) + log.Tracef("Assigning to %s IP address %s", hwaddr, ip.String()) hostname := p.ParseOptions()[dhcp4.OptionHostName] lease := &Lease{HWAddr: hwaddr, IP: ip, Hostname: string(hostname)} s.leases = append(s.leases, lease) @@ -187,7 +187,7 @@ func (s *Server) locateLease(p dhcp4.Packet) *Lease { hwaddr := p.CHAddr() for i := range s.leases { if bytes.Equal([]byte(hwaddr), []byte(s.leases[i].HWAddr)) { - // trace("bytes.Equal(%s, %s) returned true", hwaddr, s.leases[i].hwaddr) + // log.Tracef("bytes.Equal(%s, %s) returned true", hwaddr, s.leases[i].hwaddr) return s.leases[i] } } @@ -205,12 +205,12 @@ func (s *Server) findFreeIP(p dhcp4.Packet, hwaddr net.HardwareAddr) (net.IP, er for i := 0; i < dhcp4.IPRange(s.leaseStart, s.leaseStop); i++ { newIP := dhcp4.IPAdd(s.leaseStart, i) foundHWaddr := s.getIPpool(newIP) - trace("tried IP %v, got hwaddr %v", newIP, foundHWaddr) + log.Tracef("tried IP %v, got hwaddr %v", newIP, foundHWaddr) if foundHWaddr != nil && len(foundHWaddr) != 0 { // if !bytes.Equal(foundHWaddr, hwaddr) { - // trace("SHOULD NOT HAPPEN: hwaddr in IP pool %s is not equal to hwaddr in lease %s", foundHWaddr, hwaddr) + // log.Tracef("SHOULD NOT HAPPEN: hwaddr in IP pool %s is not equal to hwaddr in lease %s", foundHWaddr, hwaddr) // } - trace("will try again") + log.Tracef("will try again") continue } foundIP = newIP @@ -246,14 +246,14 @@ func (s *Server) unreserveIP(ip net.IP) { } func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dhcp4.Options) dhcp4.Packet { - trace("Got %v message", msgType) - trace("Leases:") + log.Tracef("Got %v message", msgType) + log.Tracef("Leases:") for i, lease := range s.leases { - trace("Lease #%d: hwaddr %s, ip %s, expiry %s", i, lease.HWAddr, lease.IP, lease.Expiry) + log.Tracef("Lease #%d: hwaddr %s, ip %s, expiry %s", i, lease.HWAddr, lease.IP, lease.Expiry) } - trace("IP pool:") + log.Tracef("IP pool:") for ip, hwaddr := range s.IPpool { - trace("IP pool entry %s -> %s", net.IPv4(ip[0], ip[1], ip[2], ip[3]), hwaddr) + log.Tracef("IP pool entry %s -> %s", net.IPv4(ip[0], ip[1], ip[2], ip[3]), hwaddr) } // spew.Dump(s.leases, s.IPpool) // log.Printf("Called with msgType = %v, options = %+v", msgType, options) @@ -280,22 +280,22 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh switch msgType { case dhcp4.Discover: // Broadcast Packet From Client - Can I have an IP? // find a lease, but don't update lease time - trace("Got from client: Discover") + log.Tracef("Got from client: Discover") lease, err := s.reserveLease(p) if err != nil { - trace("Couldn't find free lease: %s", err) + log.Tracef("Couldn't find free lease: %s", err) // couldn't find lease, don't respond return nil } reply := dhcp4.ReplyPacket(p, dhcp4.Offer, s.ipnet.IP, lease.IP, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) - trace("Replying with offer: offered IP %v for %v with options %+v", lease.IP, s.leaseTime, reply.ParseOptions()) + log.Tracef("Replying with offer: offered IP %v for %v with options %+v", lease.IP, s.leaseTime, reply.ParseOptions()) return reply case dhcp4.Request: // Broadcast From Client - I'll take that IP (Also start for renewals) // start/renew a lease -- update lease time // some clients (OSX) just go right ahead and do Request first from previously known IP, if they get NAK, they restart full cycle with Discover then Request - trace("Got from client: Request") + log.Tracef("Got from client: Request") if server, ok := options[dhcp4.OptionServerIdentifier]; ok && !net.IP(server).Equal(s.ipnet.IP) { - trace("Request message not for this DHCP server (%v vs %v)", p, server, s.ipnet.IP) + log.Tracef("Request message not for this DHCP server (%v vs %v)", server, s.ipnet.IP) return nil // Message not for this dhcp server } @@ -305,19 +305,19 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh } if reqIP.To4() == nil { - trace("Replying with NAK: request IP isn't valid IPv4: %s", reqIP) + log.Tracef("Replying with NAK: request IP isn't valid IPv4: %s", reqIP) return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil) } if reqIP.Equal(net.IPv4zero) { - trace("Replying with NAK: request IP is 0.0.0.0") + log.Tracef("Replying with NAK: request IP is 0.0.0.0") return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil) } - trace("requested IP is %s", reqIP) + log.Tracef("requested IP is %s", reqIP) lease, err := s.reserveLease(p) if err != nil { - trace("Couldn't find free lease: %s", err) + log.Tracef("Couldn't find free lease: %s", err) // couldn't find lease, don't respond return nil } @@ -325,7 +325,7 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh if lease.IP.Equal(reqIP) { // IP matches lease IP, nothing else to do lease.Expiry = time.Now().Add(s.leaseTime) - trace("Replying with ACK: request IP matches lease IP, nothing else to do. IP %v for %v", lease.IP, p.CHAddr()) + log.Tracef("Replying with ACK: request IP matches lease IP, nothing else to do. IP %v for %v", lease.IP, p.CHAddr()) return dhcp4.ReplyPacket(p, dhcp4.ACK, s.ipnet.IP, lease.IP, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) } @@ -333,14 +333,14 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh // requested IP different from lease // - trace("lease IP is different from requested IP: %s vs %s", lease.IP, reqIP) + log.Tracef("lease IP is different from requested IP: %s vs %s", lease.IP, reqIP) hwaddr := s.getIPpool(reqIP) if hwaddr == nil { // not in pool, check if it's in DHCP range if dhcp4.IPInRange(s.leaseStart, s.leaseStop, reqIP) { // okay, we can give it to our client -- it's in our DHCP range and not taken, so let them use their IP - trace("Replying with ACK: request IP %v is not taken, so assigning lease IP %v to it, for %v", reqIP, lease.IP, p.CHAddr()) + log.Tracef("Replying with ACK: request IP %v is not taken, so assigning lease IP %v to it, for %v", reqIP, lease.IP, p.CHAddr()) s.unreserveIP(lease.IP) lease.IP = reqIP s.reserveIP(reqIP, p.CHAddr()) @@ -355,21 +355,21 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh // requsted IP is not sufficient, reply with NAK if hwaddr != nil { - trace("Replying with NAK: request IP %s is taken, asked by %v", reqIP, p.CHAddr()) + log.Tracef("Replying with NAK: request IP %s is taken, asked by %v", reqIP, p.CHAddr()) return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil) } // requested IP is outside of DHCP range - trace("Replying with NAK: request IP %s is outside of DHCP range [%s, %s], asked by %v", reqIP, s.leaseStart, s.leaseStop, p.CHAddr()) + log.Tracef("Replying with NAK: request IP %s is outside of DHCP range [%s, %s], asked by %v", reqIP, s.leaseStart, s.leaseStop, p.CHAddr()) return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil) case dhcp4.Decline: // Broadcast From Client - Sorry I can't use that IP - trace("Got from client: Decline") + log.Tracef("Got from client: Decline") case dhcp4.Release: // From Client, I don't need that IP anymore - trace("Got from client: Release") + log.Tracef("Got from client: Release") case dhcp4.Inform: // From Client, I have this IP and there's nothing you can do about it - trace("Got from client: Inform") + log.Tracef("Got from client: Inform") // do nothing // from server -- ignore those but enumerate just in case diff --git a/dhcpd/helpers.go b/dhcpd/helpers.go index cf721f31..b47015fd 100644 --- a/dhcpd/helpers.go +++ b/dhcpd/helpers.go @@ -2,30 +2,13 @@ package dhcpd import ( "fmt" - "log" "net" - "os" - "path" - "runtime" "strings" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/joomcode/errorx" ) -func trace(format string, args ...interface{}) { - pc := make([]uintptr, 10) // at least 1 entry needed - runtime.Callers(2, pc) - f := runtime.FuncForPC(pc[0]) - var buf strings.Builder - buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name()))) - text := fmt.Sprintf(format, args...) - buf.WriteString(text) - if len(text) == 0 || text[len(text)-1] != '\n' { - buf.WriteRune('\n') - } - fmt.Fprint(os.Stderr, buf.String()) -} - func isTimeout(err error) bool { operr, ok := err.(*net.OpError) if !ok { diff --git a/dhcpd/standalone/main.go b/dhcpd/standalone/main.go index f6dac3ac..3b8c2b4c 100644 --- a/dhcpd/standalone/main.go +++ b/dhcpd/standalone/main.go @@ -1,7 +1,6 @@ package main import ( - "log" "net" "os" "os/signal" @@ -9,6 +8,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dhcpd" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/krolaw/dhcp4" ) diff --git a/dns.go b/dns.go index 7f94bc77..6ced782a 100644 --- a/dns.go +++ b/dns.go @@ -2,11 +2,11 @@ package main import ( "fmt" - "log" "net" "github.com/AdguardTeam/AdGuardHome/dnsfilter" "github.com/AdguardTeam/AdGuardHome/dnsforward" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/joomcode/errorx" ) diff --git a/dnsfilter/dnsfilter.go b/dnsfilter/dnsfilter.go index cd408a4d..5da4259c 100644 --- a/dnsfilter/dnsfilter.go +++ b/dnsfilter/dnsfilter.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io/ioutil" - "log" "net" "net/http" "regexp" @@ -17,6 +16,7 @@ import ( "sync/atomic" "time" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/bluele/gcache" "golang.org/x/net/publicsuffix" ) diff --git a/dnsfilter/dnsfilter_test.go b/dnsfilter/dnsfilter_test.go index a93fadfc..08ee2b5b 100644 --- a/dnsfilter/dnsfilter_test.go +++ b/dnsfilter/dnsfilter_test.go @@ -17,6 +17,7 @@ import ( "os" "runtime" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/shirou/gopsutil/process" "go.uber.org/goleak" ) @@ -24,7 +25,7 @@ import ( // first in file because it must be run first func TestLotsOfRulesMemoryUsage(t *testing.T) { start := getRSS() - trace("RSS before loading rules - %d kB\n", start/1024) + log.Tracef("RSS before loading rules - %d kB\n", start/1024) dumpMemProfile(_Func() + "1.pprof") d := NewForTest() @@ -35,7 +36,7 @@ func TestLotsOfRulesMemoryUsage(t *testing.T) { } afterLoad := getRSS() - trace("RSS after loading rules - %d kB (%d kB diff)\n", afterLoad/1024, (afterLoad-start)/1024) + log.Tracef("RSS after loading rules - %d kB (%d kB diff)\n", afterLoad/1024, (afterLoad-start)/1024) dumpMemProfile(_Func() + "2.pprof") tests := []struct { @@ -58,7 +59,7 @@ func TestLotsOfRulesMemoryUsage(t *testing.T) { } } afterMatch := getRSS() - trace("RSS after matching - %d kB (%d kB diff)\n", afterMatch/1024, (afterMatch-afterLoad)/1024) + log.Tracef("RSS after matching - %d kB (%d kB diff)\n", afterMatch/1024, (afterMatch-afterLoad)/1024) dumpMemProfile(_Func() + "3.pprof") } @@ -88,20 +89,20 @@ func dumpMemProfile(name string) { const topHostsFilename = "../tests/top-1m.csv" func fetchTopHostsFromNet() { - trace("Fetching top hosts from network") + log.Tracef("Fetching top hosts from network") resp, err := http.Get("http://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip") if err != nil { panic(err) } defer resp.Body.Close() - trace("Reading zipfile body") + log.Tracef("Reading zipfile body") zipfile, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } - trace("Opening zipfile") + log.Tracef("Opening zipfile") r, err := zip.NewReader(bytes.NewReader(zipfile), int64(len(zipfile))) if err != nil { panic(err) @@ -111,19 +112,19 @@ func fetchTopHostsFromNet() { panic(fmt.Errorf("zipfile must have only one entry: %+v", r)) } f := r.File[0] - trace("Unpacking file %s from zipfile", f.Name) + log.Tracef("Unpacking file %s from zipfile", f.Name) rc, err := f.Open() if err != nil { panic(err) } - trace("Reading file %s contents", f.Name) + log.Tracef("Reading file %s contents", f.Name) body, err := ioutil.ReadAll(rc) if err != nil { panic(err) } rc.Close() - trace("Writing file %s contents to disk", f.Name) + log.Tracef("Writing file %s contents to disk", f.Name) err = ioutil.WriteFile(topHostsFilename+".tmp", body, 0644) if err != nil { panic(err) @@ -144,16 +145,16 @@ func getTopHosts() { func TestLotsOfRulesLotsOfHostsMemoryUsage(t *testing.T) { start := getRSS() - trace("RSS before loading rules - %d kB\n", start/1024) + log.Tracef("RSS before loading rules - %d kB\n", start/1024) dumpMemProfile(_Func() + "1.pprof") d := NewForTest() defer d.Destroy() mustLoadTestRules(d) - trace("Have %d rules", d.Count()) + log.Tracef("Have %d rules", d.Count()) afterLoad := getRSS() - trace("RSS after loading rules - %d kB (%d kB diff)\n", afterLoad/1024, (afterLoad-start)/1024) + log.Tracef("RSS after loading rules - %d kB (%d kB diff)\n", afterLoad/1024, (afterLoad-start)/1024) dumpMemProfile(_Func() + "2.pprof") getTopHosts() @@ -163,7 +164,7 @@ func TestLotsOfRulesLotsOfHostsMemoryUsage(t *testing.T) { } defer hostnames.Close() afterHosts := getRSS() - trace("RSS after loading hosts - %d kB (%d kB diff)\n", afterHosts/1024, (afterHosts-afterLoad)/1024) + log.Tracef("RSS after loading hosts - %d kB (%d kB diff)\n", afterHosts/1024, (afterHosts-afterLoad)/1024) dumpMemProfile(_Func() + "2.pprof") { @@ -182,7 +183,7 @@ func TestLotsOfRulesLotsOfHostsMemoryUsage(t *testing.T) { } afterMatch := getRSS() - trace("RSS after matching - %d kB (%d kB diff)\n", afterMatch/1024, (afterMatch-afterLoad)/1024) + log.Tracef("RSS after matching - %d kB (%d kB diff)\n", afterMatch/1024, (afterMatch-afterLoad)/1024) dumpMemProfile(_Func() + "3.pprof") } @@ -236,7 +237,7 @@ func TestSuffixRule(t *testing.T) { t.Errorf("Result suffix does not match for \"%s\": got \"%s\" expected \"%s\"", testcase.rule, suffix, testcase.suffix) continue } - // trace("\"%s\": %v: %s", testcase.rule, isSuffix, suffix) + // log.Tracef("\"%s\": %v: %s", testcase.rule, isSuffix, suffix) } } diff --git a/dnsfilter/helpers.go b/dnsfilter/helpers.go index 8152f402..68d4ba26 100644 --- a/dnsfilter/helpers.go +++ b/dnsfilter/helpers.go @@ -1,10 +1,6 @@ package dnsfilter import ( - "fmt" - "os" - "path" - "runtime" "strings" "sync/atomic" ) @@ -62,17 +58,3 @@ func updateMax(valuePtr *int64, maxPtr *int64) { // swapping failed because value has changed after reading, try again } } - -func trace(format string, args ...interface{}) { - pc := make([]uintptr, 10) // at least 1 entry needed - runtime.Callers(2, pc) - f := runtime.FuncForPC(pc[0]) - var buf strings.Builder - buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name()))) - text := fmt.Sprintf(format, args...) - buf.WriteString(text) - if len(text) == 0 || text[len(text)-1] != '\n' { - buf.WriteRune('\n') - } - fmt.Fprint(os.Stderr, buf.String()) -} diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index a814f660..f60b0cf4 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -3,13 +3,13 @@ package dnsforward import ( "errors" "fmt" - "log" "net" "strings" "sync" "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/joomcode/errorx" @@ -283,7 +283,7 @@ func (s *Server) filterDNSRequest(d *proxy.DNSContext) (*dnsfilter.Result, error // Return immediately if there's an error return nil, errorx.Decorate(err, "dnsfilter failed to check host '%s'", host) } else if res.IsFiltered { - // trace("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule) + // log.Tracef("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule) d.Res = s.genDNSFilterMessage(d, &res) } diff --git a/dnsforward/querylog.go b/dnsforward/querylog.go index de740d1a..dd7bf2c2 100644 --- a/dnsforward/querylog.go +++ b/dnsforward/querylog.go @@ -3,7 +3,6 @@ package dnsforward import ( "encoding/json" "fmt" - "log" "net" "net/http" "strconv" @@ -12,6 +11,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/miekg/dns" ) diff --git a/dnsforward/querylog_file.go b/dnsforward/querylog_file.go index 19097baa..39f9cc46 100644 --- a/dnsforward/querylog_file.go +++ b/dnsforward/querylog_file.go @@ -5,11 +5,11 @@ import ( "compress/gzip" "encoding/json" "fmt" - "log" "os" "sync" "time" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/go-test/deep" ) @@ -221,7 +221,7 @@ func genericLoader(onEntry func(entry *logEntry) error, needMore func() bool, ti } if now.Sub(entry.Time) > timeWindow { - // trace("skipping entry") // debug logging + // log.Tracef("skipping entry") // debug logging continue } diff --git a/dnsforward/querylog_top.go b/dnsforward/querylog_top.go index b78dea79..563e64ca 100644 --- a/dnsforward/querylog_top.go +++ b/dnsforward/querylog_top.go @@ -3,7 +3,6 @@ package dnsforward import ( "bytes" "fmt" - "log" "net/http" "os" "path" @@ -14,6 +13,7 @@ import ( "sync" "time" + "github.com/AdguardTeam/AdGuardHome/log" "github.com/bluele/gcache" "github.com/miekg/dns" ) diff --git a/dnsforward/stats.go b/dnsforward/stats.go index 9cfe5f58..47a881f1 100644 --- a/dnsforward/stats.go +++ b/dnsforward/stats.go @@ -3,12 +3,12 @@ package dnsforward import ( "encoding/json" "fmt" - "log" "net/http" "sync" "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" + "github.com/AdguardTeam/AdGuardHome/log" ) var ( @@ -69,7 +69,7 @@ func purgeStats() { func (p *periodicStats) Inc(name string, when time.Time) { // calculate how many periods ago this happened elapsed := int64(time.Since(when) / p.period) - // trace("%s: %v as %v -> [%v]", name, time.Since(when), p.period, elapsed) + // log.Tracef("%s: %v as %v -> [%v]", name, time.Since(when), p.period, elapsed) if elapsed >= statsHistoryElements { return // outside of our timeframe } @@ -83,7 +83,7 @@ func (p *periodicStats) Inc(name string, when time.Time) { func (p *periodicStats) Observe(name string, when time.Time, value float64) { // calculate how many periods ago this happened elapsed := int64(time.Since(when) / p.period) - // trace("%s: %v as %v -> [%v]", name, time.Since(when), p.period, elapsed) + // log.Tracef("%s: %v as %v -> [%v]", name, time.Since(when), p.period, elapsed) if elapsed >= statsHistoryElements { return // outside of our timeframe } @@ -92,7 +92,7 @@ func (p *periodicStats) Observe(name string, when time.Time, value float64) { countname := name + "_count" currentValues := p.Entries[countname] value := currentValues[elapsed] - // trace("Will change p.Entries[%s][%d] from %v to %v", countname, elapsed, value, value+1) + // log.Tracef("Will change p.Entries[%s][%d] from %v to %v", countname, elapsed, value, value+1) value += 1 currentValues[elapsed] = value p.Entries[countname] = currentValues @@ -145,7 +145,7 @@ type counter struct { } func newDNSCounter(name string) *counter { - // trace("called") + // log.Tracef("called") return &counter{ name: name, } diff --git a/filter.go b/filter.go index 1150d292..b6bc4051 100644 --- a/filter.go +++ b/filter.go @@ -3,7 +3,6 @@ package main import ( "fmt" "io/ioutil" - "log" "os" "path/filepath" "reflect" @@ -13,6 +12,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" + "github.com/AdguardTeam/AdGuardHome/log" ) var ( diff --git a/helpers.go b/helpers.go index cf3156da..3ba42544 100644 --- a/helpers.go +++ b/helpers.go @@ -3,7 +3,6 @@ package main import ( "bufio" "errors" - "fmt" "io" "io/ioutil" "net/http" @@ -135,17 +134,3 @@ func _Func() string { f := runtime.FuncForPC(pc[0]) return path.Base(f.Name()) } - -func trace(format string, args ...interface{}) { - pc := make([]uintptr, 10) // at least 1 entry needed - runtime.Callers(2, pc) - f := runtime.FuncForPC(pc[0]) - var buf strings.Builder - buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name()))) - text := fmt.Sprintf(format, args...) - buf.WriteString(text) - if len(text) == 0 || text[len(text)-1] != '\n' { - buf.WriteRune('\n') - } - fmt.Fprint(os.Stderr, buf.String()) -} diff --git a/i18n.go b/i18n.go index 4a52813a..80afd70a 100644 --- a/i18n.go +++ b/i18n.go @@ -3,9 +3,10 @@ package main import ( "fmt" "io/ioutil" - "log" "net/http" "strings" + + "github.com/AdguardTeam/AdGuardHome/log" ) // -------------------- diff --git a/log/log.go b/log/log.go index db6132e7..ba564153 100644 --- a/log/log.go +++ b/log/log.go @@ -12,12 +12,34 @@ import ( var Verbose = false +// Print calls Output to print to the standard logger. +// Arguments are handled in the manner of fmt.Print. +func Print(v ...interface{}) { + log.Print(v...) +} + // Printf calls Output to print to the standard logger. // Arguments are handled in the manner of fmt.Printf. func Printf(format string, v ...interface{}) { log.Printf(format, v...) } +// Println calls Output to print to the standard logger. +// Arguments are handled in the manner of fmt.Println. +func Println(v ...interface{}) { + log.Println(v...) +} + +// Fatal is equivalent to Print() followed by a call to os.Exit(1). +func Fatal(v ...interface{}) { + log.Fatal(v...) +} + +// Fatalf is equivalent to Printf() followed by a call to os.Exit(1). +func Fatalf(format string, v ...interface{}) { + log.Fatalf(format, v...) +} + func Tracef(format string, v ...interface{}) { if Verbose { pc := make([]uintptr, 10) // at least 1 entry needed diff --git a/upgrade.go b/upgrade.go index 21d7686d..582a9d0c 100644 --- a/upgrade.go +++ b/upgrade.go @@ -3,10 +3,10 @@ package main import ( "fmt" "io/ioutil" - "log" "os" "path/filepath" + "github.com/AdguardTeam/AdGuardHome/log" "gopkg.in/yaml.v2" ) From 568784b9921fe62653a992ada3348a60d89e5b28 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Sat, 29 Dec 2018 18:43:17 +0300 Subject: [PATCH 41/45] Added check for active DHCP before enable --- client/src/__locales/en.json | 2 +- client/src/actions/index.js | 102 ++++++++++----- client/src/api/Api.js | 8 +- client/src/components/Settings/Dhcp/Form.js | 86 +------------ .../src/components/Settings/Dhcp/Interface.js | 113 +++++++++++++++++ client/src/components/Settings/Dhcp/index.js | 116 +++++++++++++----- client/src/components/Settings/Settings.css | 4 + client/src/reducers/index.js | 1 + 8 files changed, 282 insertions(+), 150 deletions(-) create mode 100644 client/src/components/Settings/Dhcp/Interface.js diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 243ed012..fac7ceaa 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -1,5 +1,5 @@ { - "refresh_status": "Refresh status", + "check_dhcp_servers": "Check for DHCP servers", "save_config": "Save config", "enabled_dhcp": "DHCP server enabled", "disabled_dhcp": "DHCP server disabled", diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 20ef3766..949e3a5d 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -553,31 +553,14 @@ export const getDhcpInterfaces = () => async (dispatch) => { } }; -export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST'); -export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS'); -export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE'); - -export const setDhcpConfig = config => async (dispatch) => { - dispatch(setDhcpConfigRequest()); - try { - await apiClient.setDhcpConfig(config); - dispatch(getDhcpStatus()); - dispatch(addSuccessToast('dhcp_config_saved')); - dispatch(setDhcpConfigSuccess()); - } catch (error) { - dispatch(addErrorToast({ error })); - dispatch(setDhcpConfigFailure()); - } -}; - export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST'); export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS'); export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE'); -export const findActiveDhcp = () => async (dispatch) => { +export const findActiveDhcp = name => async (dispatch) => { dispatch(findActiveDhcpRequest()); try { - const activeDhcp = await apiClient.findActiveDhcp(); + const activeDhcp = await apiClient.findActiveDhcp(name); dispatch(findActiveDhcpSuccess(activeDhcp)); } catch (error) { dispatch(addErrorToast({ error })); @@ -585,27 +568,84 @@ export const findActiveDhcp = () => async (dispatch) => { } }; +export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST'); +export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS'); +export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE'); + +// TODO rewrite findActiveDhcp part +export const setDhcpConfig = config => async (dispatch) => { + dispatch(setDhcpConfigRequest()); + try { + if (config.interface_name) { + dispatch(findActiveDhcpRequest()); + try { + const activeDhcp = await apiClient.findActiveDhcp(config.interface_name); + dispatch(findActiveDhcpSuccess(activeDhcp)); + + if (!activeDhcp.found) { + await apiClient.setDhcpConfig(config); + dispatch(addSuccessToast('dhcp_config_saved')); + dispatch(setDhcpConfigSuccess()); + dispatch(getDhcpStatus()); + } else { + dispatch(addErrorToast({ error: 'dhcp_found' })); + } + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(findActiveDhcpFailure()); + } + } else { + await apiClient.setDhcpConfig(config); + dispatch(addSuccessToast('dhcp_config_saved')); + dispatch(setDhcpConfigSuccess()); + dispatch(getDhcpStatus()); + } + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(setDhcpConfigFailure()); + } +}; + export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST'); export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE'); export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS'); +// TODO rewrite findActiveDhcp part export const toggleDhcp = config => async (dispatch) => { dispatch(toggleDhcpRequest()); - let successMessage = ''; - try { - if (config.enabled) { - successMessage = 'disabled_dhcp'; + if (config.enabled) { + dispatch(addSuccessToast('disabled_dhcp')); + try { await apiClient.setDhcpConfig({ ...config, enabled: false }); - } else { - successMessage = 'enabled_dhcp'; - await apiClient.setDhcpConfig({ ...config, enabled: true }); + dispatch(toggleDhcpSuccess()); + dispatch(getDhcpStatus()); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(toggleDhcpFailure()); } + } else { + dispatch(findActiveDhcpRequest()); + try { + const activeDhcp = await apiClient.findActiveDhcp(config.interface_name); + dispatch(findActiveDhcpSuccess(activeDhcp)); - dispatch(addSuccessToast(successMessage)); - dispatch(toggleDhcpSuccess()); - } catch (error) { - dispatch(addErrorToast({ error })); - dispatch(toggleDhcpFailure()); + if (!activeDhcp.found) { + try { + await apiClient.setDhcpConfig({ ...config, enabled: true }); + dispatch(toggleDhcpSuccess()); + dispatch(getDhcpStatus()); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(toggleDhcpFailure()); + } + dispatch(addSuccessToast('enabled_dhcp')); + } else { + dispatch(addErrorToast({ error: 'dhcp_found' })); + } + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(findActiveDhcpFailure()); + } } }; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 2bb0277a..4592fae7 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -328,8 +328,12 @@ export default class Api { return this.makeRequest(path, method, parameters); } - findActiveDhcp() { + findActiveDhcp(name) { const { path, method } = this.DHCP_FIND_ACTIVE; - return this.makeRequest(path, method); + const parameters = { + data: name, + headers: { 'Content-Type': 'text/plain' }, + }; + return this.makeRequest(path, method, parameters); } } diff --git a/client/src/components/Settings/Dhcp/Form.js b/client/src/components/Settings/Dhcp/Form.js index 14455b1b..9fc497d2 100644 --- a/client/src/components/Settings/Dhcp/Form.js +++ b/client/src/components/Settings/Dhcp/Form.js @@ -1,7 +1,6 @@ import React, { Fragment } from 'react'; -import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { Field, reduxForm, formValueSelector } from 'redux-form'; +import { Field, reduxForm } from 'redux-form'; import { withNamespaces, Trans } from 'react-i18next'; import flow from 'lodash/flow'; @@ -45,88 +44,17 @@ const renderField = ({ ); -const renderInterfaces = (interfaces => ( - Object.keys(interfaces).map((item) => { - const option = interfaces[item]; - const { name } = option; - const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':')); - let interfaceIP = option.ip_addresses[0]; - - if (!onlyIPv6) { - option.ip_addresses.forEach((ip) => { - if (!ip.includes(':')) { - interfaceIP = ip; - } - }); - } - - return ( - - ); - }) -)); - -const renderInterfaceValues = (interfaceValues => ( -
    -
  • - MTU: - {interfaceValues.mtu} -
  • -
  • - dhcp_hardware_address: - {interfaceValues.hardware_address} -
  • -
  • - dhcp_ip_addresses: - { - interfaceValues.ip_addresses - .map(ip => {ip}) - } -
  • -
-)); - -let Form = (props) => { +const Form = (props) => { const { t, handleSubmit, pristine, submitting, - interfaces, - processing, - interfaceValue, } = props; return (
-
- {!processing && interfaces && -
-
-
- - - - {renderInterfaces(interfaces)} - -
-
- {interfaceValue && -
- {renderInterfaceValues(interfaces[interfaceValue])} -
- } -
- } -
-
@@ -211,20 +139,10 @@ Form.propTypes = { submitting: PropTypes.bool, interfaces: PropTypes.object, processing: PropTypes.bool, - interfaceValue: PropTypes.string, initialValues: PropTypes.object, t: PropTypes.func, }; -const selector = formValueSelector('dhcpForm'); - -Form = connect((state) => { - const interfaceValue = selector(state, 'interface_name'); - return { - interfaceValue, - }; -})(Form); - export default flow([ withNamespaces(), reduxForm({ form: 'dhcpForm' }), diff --git a/client/src/components/Settings/Dhcp/Interface.js b/client/src/components/Settings/Dhcp/Interface.js new file mode 100644 index 00000000..fe4206a7 --- /dev/null +++ b/client/src/components/Settings/Dhcp/Interface.js @@ -0,0 +1,113 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { Field, reduxForm, formValueSelector } from 'redux-form'; +import { withNamespaces, Trans } from 'react-i18next'; +import flow from 'lodash/flow'; + +const renderInterfaces = (interfaces => ( + Object.keys(interfaces).map((item) => { + const option = interfaces[item]; + const { name } = option; + const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':')); + let interfaceIP = option.ip_addresses[0]; + + if (!onlyIPv6) { + option.ip_addresses.forEach((ip) => { + if (!ip.includes(':')) { + interfaceIP = ip; + } + }); + } + + return ( + + ); + }) +)); + +const renderInterfaceValues = (interfaceValues => ( +
    +
  • + MTU: + {interfaceValues.mtu} +
  • +
  • + dhcp_hardware_address: + {interfaceValues.hardware_address} +
  • +
  • + dhcp_ip_addresses: + { + interfaceValues.ip_addresses + .map(ip => {ip}) + } +
  • +
+)); + +let Interface = (props) => { + const { + t, + handleChange, + interfaces, + processing, + interfaceValue, + enabled, + } = props; + + return ( + + {!processing && interfaces && +
+
+
+ + + + {renderInterfaces(interfaces)} + +
+
+ {interfaceValue && +
+ {renderInterfaceValues(interfaces[interfaceValue])} +
+ } +
+ } +
+ + ); +}; + +Interface.propTypes = { + handleChange: PropTypes.func, + interfaces: PropTypes.object, + processing: PropTypes.bool, + interfaceValue: PropTypes.string, + initialValues: PropTypes.object, + enabled: PropTypes.bool, + t: PropTypes.func, +}; + +const selector = formValueSelector('dhcpInterface'); + +Interface = connect((state) => { + const interfaceValue = selector(state, 'interface_name'); + return { + interfaceValue, + }; +})(Interface); + +export default flow([ + withNamespaces(), + reduxForm({ form: 'dhcpInterface' }), +])(Interface); diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js index d18a9b93..3beb136f 100644 --- a/client/src/components/Settings/Dhcp/index.js +++ b/client/src/components/Settings/Dhcp/index.js @@ -5,6 +5,7 @@ import { Trans, withNamespaces } from 'react-i18next'; import Form from './Form'; import Leases from './Leases'; +import Interface from './Interface'; import Card from '../../ui/Card'; class Dhcp extends Component { @@ -12,27 +13,80 @@ class Dhcp extends Component { this.props.setDhcpConfig(values); }; - handleRefresh = () => { - this.props.findActiveDhcp(); + handleFormChange = (value) => { + this.props.setDhcpConfig(value); + } + + handleToggle = (config) => { + this.props.toggleDhcp(config); + this.props.findActiveDhcp(config.interface_name); } getToggleDhcpButton = () => { - const { config } = this.props.dhcp; - const buttonText = config.enabled ? 'dhcp_disable' : 'dhcp_enable'; - const buttonClass = config.enabled ? 'btn-gray' : 'btn-success'; + const { config, active } = this.props.dhcp; + const activeDhcpFound = active && active.found; + const filledConfig = Object.keys(config).every((key) => { + if (key === 'enabled') { + return true; + } + + return config[key]; + }); + + if (config.enabled) { + return ( + + ); + } return ( ); } + getActiveDhcpMessage = () => { + const { active } = this.props.dhcp; + + if (active) { + if (active.error) { + return ( +
+ {active.error} +
+ ); + } + + return ( + + {active.found ? ( +
+ dhcp_found +
+ ) : ( +
+ dhcp_not_found +
+ )} +
+ ); + } + + return ''; + } + render() { const { t, dhcp } = this.props; const statusButtonClass = classnames({ @@ -42,14 +96,20 @@ class Dhcp extends Component { return ( - {!dhcp.processing && - -
-
+ +
+ {!dhcp.processing && + +
@@ -57,29 +117,21 @@ class Dhcp extends Component {
{this.getToggleDhcpButton()}
- {dhcp.active && -
- {dhcp.active.found ? ( - - dhcp_found - - ) : ( - dhcp_not_found - )} -
- } -
-
- - } + {this.getActiveDhcpMessage()} + + } +
+
{!dhcp.processing && dhcp.config.enabled &&
diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css index 22e15b16..a4ff2394 100644 --- a/client/src/components/Settings/Settings.css +++ b/client/src/components/Settings/Settings.css @@ -44,3 +44,7 @@ .interface__ip:last-child:after { content: ""; } + +.dhcp { + min-height: 450px; +} diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 53ba15ca..fc202a0b 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -304,6 +304,7 @@ const dhcp = handleActions({ config: { enabled: false, }, + active: null, leases: [], }); From 368e2d1ebd51bbb48a6df208299630666853f985 Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 19:12:22 +0300 Subject: [PATCH 42/45] move log wrapper library outside into hmage/golibs/log --- app.go | 2 +- config.go | 2 +- control.go | 2 +- dhcp.go | 2 +- dhcpd/check_other_dhcp.go | 2 +- dhcpd/dhcpd.go | 2 +- dhcpd/helpers.go | 2 +- dhcpd/standalone/main.go | 2 +- dns.go | 2 +- dnsfilter/dnsfilter.go | 2 +- dnsfilter/dnsfilter_test.go | 2 +- dnsforward/dnsforward.go | 2 +- dnsforward/querylog.go | 2 +- dnsforward/querylog_file.go | 2 +- dnsforward/querylog_top.go | 2 +- dnsforward/stats.go | 2 +- filter.go | 2 +- go.mod | 1 + go.sum | 2 ++ i18n.go | 2 +- log/log.go | 57 ------------------------------------- upgrade.go | 2 +- 22 files changed, 22 insertions(+), 76 deletions(-) delete mode 100644 log/log.go diff --git a/app.go b/app.go index 4d5e53a9..9439eb8a 100644 --- a/app.go +++ b/app.go @@ -12,8 +12,8 @@ import ( "syscall" "time" - "github.com/AdguardTeam/AdGuardHome/log" "github.com/gobuffalo/packr" + "github.com/hmage/golibs/log" "golang.org/x/crypto/ssh/terminal" ) diff --git a/config.go b/config.go index 8fc7255d..f759ff4a 100644 --- a/config.go +++ b/config.go @@ -9,7 +9,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/dhcpd" "github.com/AdguardTeam/AdGuardHome/dnsfilter" "github.com/AdguardTeam/AdGuardHome/dnsforward" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" "gopkg.in/yaml.v2" ) diff --git a/control.go b/control.go index b15e2a25..e35c2529 100644 --- a/control.go +++ b/control.go @@ -12,8 +12,8 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dnsforward" - "github.com/AdguardTeam/AdGuardHome/log" "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/hmage/golibs/log" "github.com/miekg/dns" "gopkg.in/asaskevich/govalidator.v4" ) diff --git a/dhcp.go b/dhcp.go index 983f755a..4a54e82f 100644 --- a/dhcp.go +++ b/dhcp.go @@ -10,7 +10,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dhcpd" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" "github.com/joomcode/errorx" ) diff --git a/dhcpd/check_other_dhcp.go b/dhcpd/check_other_dhcp.go index 5d81953c..661b2daa 100644 --- a/dhcpd/check_other_dhcp.go +++ b/dhcpd/check_other_dhcp.go @@ -9,7 +9,7 @@ import ( "os" "time" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" "github.com/krolaw/dhcp4" ) diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index d663121a..2b1198e4 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -6,7 +6,7 @@ import ( "net" "time" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" "github.com/krolaw/dhcp4" ) diff --git a/dhcpd/helpers.go b/dhcpd/helpers.go index b47015fd..20793f52 100644 --- a/dhcpd/helpers.go +++ b/dhcpd/helpers.go @@ -5,7 +5,7 @@ import ( "net" "strings" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" "github.com/joomcode/errorx" ) diff --git a/dhcpd/standalone/main.go b/dhcpd/standalone/main.go index 3b8c2b4c..fff02664 100644 --- a/dhcpd/standalone/main.go +++ b/dhcpd/standalone/main.go @@ -8,7 +8,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dhcpd" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" "github.com/krolaw/dhcp4" ) diff --git a/dns.go b/dns.go index 6ced782a..54e56184 100644 --- a/dns.go +++ b/dns.go @@ -6,8 +6,8 @@ import ( "github.com/AdguardTeam/AdGuardHome/dnsfilter" "github.com/AdguardTeam/AdGuardHome/dnsforward" - "github.com/AdguardTeam/AdGuardHome/log" "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/hmage/golibs/log" "github.com/joomcode/errorx" ) diff --git a/dnsfilter/dnsfilter.go b/dnsfilter/dnsfilter.go index 5da4259c..51204e1b 100644 --- a/dnsfilter/dnsfilter.go +++ b/dnsfilter/dnsfilter.go @@ -16,8 +16,8 @@ import ( "sync/atomic" "time" - "github.com/AdguardTeam/AdGuardHome/log" "github.com/bluele/gcache" + "github.com/hmage/golibs/log" "golang.org/x/net/publicsuffix" ) diff --git a/dnsfilter/dnsfilter_test.go b/dnsfilter/dnsfilter_test.go index 08ee2b5b..02feb0c7 100644 --- a/dnsfilter/dnsfilter_test.go +++ b/dnsfilter/dnsfilter_test.go @@ -17,7 +17,7 @@ import ( "os" "runtime" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" "github.com/shirou/gopsutil/process" "go.uber.org/goleak" ) diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index f60b0cf4..a242f460 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -9,9 +9,9 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" - "github.com/AdguardTeam/AdGuardHome/log" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/hmage/golibs/log" "github.com/joomcode/errorx" "github.com/miekg/dns" ) diff --git a/dnsforward/querylog.go b/dnsforward/querylog.go index dd7bf2c2..901fa1f3 100644 --- a/dnsforward/querylog.go +++ b/dnsforward/querylog.go @@ -11,7 +11,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" "github.com/miekg/dns" ) diff --git a/dnsforward/querylog_file.go b/dnsforward/querylog_file.go index 39f9cc46..930c4a8d 100644 --- a/dnsforward/querylog_file.go +++ b/dnsforward/querylog_file.go @@ -9,8 +9,8 @@ import ( "sync" "time" - "github.com/AdguardTeam/AdGuardHome/log" "github.com/go-test/deep" + "github.com/hmage/golibs/log" ) var ( diff --git a/dnsforward/querylog_top.go b/dnsforward/querylog_top.go index 563e64ca..2e55ffd7 100644 --- a/dnsforward/querylog_top.go +++ b/dnsforward/querylog_top.go @@ -13,8 +13,8 @@ import ( "sync" "time" - "github.com/AdguardTeam/AdGuardHome/log" "github.com/bluele/gcache" + "github.com/hmage/golibs/log" "github.com/miekg/dns" ) diff --git a/dnsforward/stats.go b/dnsforward/stats.go index 47a881f1..5791e1dc 100644 --- a/dnsforward/stats.go +++ b/dnsforward/stats.go @@ -8,7 +8,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" ) var ( diff --git a/filter.go b/filter.go index b6bc4051..a776f458 100644 --- a/filter.go +++ b/filter.go @@ -12,7 +12,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/dnsfilter" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" ) var ( diff --git a/go.mod b/go.mod index 41a97633..81fe1a75 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-test/deep v1.0.1 github.com/gobuffalo/packr v1.19.0 + github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4 github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86 github.com/joomcode/errorx v0.1.0 github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 diff --git a/go.sum b/go.sum index 4c18efc7..6e2c7424 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264 h1:roWyi0eEdiFreSq github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= github.com/gobuffalo/packr v1.19.0 h1:3UDmBDxesCOPF8iZdMDBBWKfkBoYujIMIZePnobqIUI= github.com/gobuffalo/packr v1.19.0/go.mod h1:MstrNkfCQhd5o+Ct4IJ0skWlxN8emOq8DsoT1G98VIU= +github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4 h1:FMAReGTEDNr4AdbScv/PqzjMQUpkkVHiF/t8sDHQQVQ= +github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4/go.mod h1:H6Ev6svFxUVPFThxLtdnFfcE9e3GWufpfmcVFpqV6HM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86 h1:Olj4M6T1omUfx7yTTcnhLf4xo6gYMmRHSJIfeA1NZy0= github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86/go.mod h1:j/ONpSHHmPgDwmFKXg9vhQvIjADe/ft1X4a3TVOmp9g= diff --git a/i18n.go b/i18n.go index 80afd70a..5b8ad62f 100644 --- a/i18n.go +++ b/i18n.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" ) // -------------------- diff --git a/log/log.go b/log/log.go deleted file mode 100644 index ba564153..00000000 --- a/log/log.go +++ /dev/null @@ -1,57 +0,0 @@ -// Wrapper for standard library log, with the only difference is that it has extra function Tracef() and optional verbose flag to enable output from that. -package log - -import ( - "fmt" - "log" - "os" - "path" - "runtime" - "strings" -) - -var Verbose = false - -// Print calls Output to print to the standard logger. -// Arguments are handled in the manner of fmt.Print. -func Print(v ...interface{}) { - log.Print(v...) -} - -// Printf calls Output to print to the standard logger. -// Arguments are handled in the manner of fmt.Printf. -func Printf(format string, v ...interface{}) { - log.Printf(format, v...) -} - -// Println calls Output to print to the standard logger. -// Arguments are handled in the manner of fmt.Println. -func Println(v ...interface{}) { - log.Println(v...) -} - -// Fatal is equivalent to Print() followed by a call to os.Exit(1). -func Fatal(v ...interface{}) { - log.Fatal(v...) -} - -// Fatalf is equivalent to Printf() followed by a call to os.Exit(1). -func Fatalf(format string, v ...interface{}) { - log.Fatalf(format, v...) -} - -func Tracef(format string, v ...interface{}) { - if Verbose { - pc := make([]uintptr, 10) // at least 1 entry needed - runtime.Callers(2, pc) - f := runtime.FuncForPC(pc[0]) - var buf strings.Builder - buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name()))) - text := fmt.Sprintf(format, v...) - buf.WriteString(text) - if len(text) == 0 || text[len(text)-1] != '\n' { - buf.WriteRune('\n') - } - fmt.Fprint(os.Stderr, buf.String()) - } -} diff --git a/upgrade.go b/upgrade.go index 582a9d0c..452df4d0 100644 --- a/upgrade.go +++ b/upgrade.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/AdguardTeam/AdGuardHome/log" + "github.com/hmage/golibs/log" "gopkg.in/yaml.v2" ) From 4d3f1b83a62c8c7bd4b828336efb4ca7ae536c9e Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 19:13:00 +0300 Subject: [PATCH 43/45] Fix race conditions found by -race --- dhcpd/dhcpd.go | 9 ++++++++- dnsforward/stats.go | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 2b1198e4..deeaac02 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net" + "sync" "time" "github.com/hmage/golibs/log" @@ -47,6 +48,7 @@ type Server struct { IPpool map[[4]byte]net.HardwareAddr ServerConfig + sync.RWMutex } // Start will listen on port 67 and serve DHCP requests. @@ -179,7 +181,9 @@ func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) { log.Tracef("Assigning to %s IP address %s", hwaddr, ip.String()) hostname := p.ParseOptions()[dhcp4.OptionHostName] lease := &Lease{HWAddr: hwaddr, IP: ip, Hostname: string(hostname)} + s.Lock() s.leases = append(s.leases, lease) + s.Unlock() return lease, nil } @@ -387,5 +391,8 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh } func (s *Server) Leases() []*Lease { - return s.leases + s.RLock() + result := s.leases + s.RUnlock() + return result } diff --git a/dnsforward/stats.go b/dnsforward/stats.go index 5791e1dc..9d11504b 100644 --- a/dnsforward/stats.go +++ b/dnsforward/stats.go @@ -142,6 +142,8 @@ func statsRotator() { type counter struct { name string // used as key in periodic stats value int64 + + sync.Mutex } func newDNSCounter(name string) *counter { @@ -156,7 +158,9 @@ func (c *counter) IncWithTime(when time.Time) { statistics.PerMinute.Inc(c.name, when) statistics.PerHour.Inc(c.name, when) statistics.PerDay.Inc(c.name, when) + c.Lock() c.value++ + c.Unlock() } func (c *counter) Inc() { @@ -167,6 +171,8 @@ type histogram struct { name string // used as key in periodic stats count int64 total float64 + + sync.Mutex } func newDNSHistogram(name string) *histogram { @@ -180,8 +186,10 @@ func (h *histogram) ObserveWithTime(value float64, when time.Time) { statistics.PerMinute.Observe(h.name, when, value) statistics.PerHour.Observe(h.name, when, value) statistics.PerDay.Observe(h.name, when, value) + h.Lock() h.count++ h.total += value + h.Unlock() } func (h *histogram) Observe(value float64) { From ec0b8c687a0a1e00eb947f708b006c89420deece Mon Sep 17 00:00:00 2001 From: Eugene Bujak Date: Sat, 29 Dec 2018 19:28:46 +0300 Subject: [PATCH 44/45] Update dnsproxy and dnscrypt, and run go mod tidy. --- go.mod | 8 +------- go.sum | 36 +++++++++--------------------------- 2 files changed, 10 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 81fe1a75..5b9b68f9 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,16 @@ module github.com/AdguardTeam/AdGuardHome require ( - github.com/AdguardTeam/dnsproxy v0.9.3 + github.com/AdguardTeam/dnsproxy v0.9.9 github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f // indirect - github.com/ameshkov/dnscrypt v1.0.1 - github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 github.com/bluele/gcache v0.0.0-20171010155617-472614239ac7 - github.com/davecgh/go-spew v1.1.1 github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-test/deep v1.0.1 github.com/gobuffalo/packr v1.19.0 github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4 - github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86 github.com/joomcode/errorx v0.1.0 github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 github.com/miekg/dns v1.1.1 - github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/pkg/errors v0.8.0 github.com/shirou/gopsutil v2.18.10+incompatible github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect go.uber.org/goleak v0.10.0 diff --git a/go.sum b/go.sum index 6e2c7424..370c62f4 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,15 @@ -github.com/AdguardTeam/dnsproxy v0.9.0 h1:doHDmVE9bV1fhiBV8rX76WWaSAB9w1H3u8WIiez5OFs= -github.com/AdguardTeam/dnsproxy v0.9.0/go.mod h1:CKZVVknYdoHVirXqqbALEkC+DBY65yCQrzSKYS78GoE= -github.com/AdguardTeam/dnsproxy v0.9.1 h1:+F6jqrVOrUjpbzhALjtbwqHfxW4M2YS3mYdhGxLXQ08= -github.com/AdguardTeam/dnsproxy v0.9.1/go.mod h1:CKZVVknYdoHVirXqqbALEkC+DBY65yCQrzSKYS78GoE= -github.com/AdguardTeam/dnsproxy v0.9.2 h1:P3B2IECZejGv8sxjyLXDbCKMgWqUEFb5rq67lxXCKZ0= -github.com/AdguardTeam/dnsproxy v0.9.2/go.mod h1:CKZVVknYdoHVirXqqbALEkC+DBY65yCQrzSKYS78GoE= -github.com/AdguardTeam/dnsproxy v0.9.3 h1:zgLcsMEQ0hPhU0LjFwPMz4qeXDF+Yy1MO9xc9QaGjbk= -github.com/AdguardTeam/dnsproxy v0.9.3/go.mod h1:GsppU3a1x0hIRtIh7Te8CWHKNHtJaoRXQh08DSRqk0A= +github.com/AdguardTeam/dnsproxy v0.9.9 h1:eFBqEZbWi0IEhux7abNP5VaS91caqR7a1W44Tfi99As= +github.com/AdguardTeam/dnsproxy v0.9.9/go.mod h1:IqBhopgNpzB168kMurbjXf86dn50geasBIuGVxY63j0= github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f h1:5ZfJxyXo8KyX8DgGXC5B7ILL8y51fci/qYz2B4j8iLY= github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 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= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= -github.com/ameshkov/dnscrypt v1.0.0 h1:Y7YexPCxtVCTDXlXu9n17+1H5YS25vftx8vV8Dhuu+E= -github.com/ameshkov/dnscrypt v1.0.0/go.mod h1:EC7Z1GguyEEwhuLXrcgkRTE3GdyPDSWq2OXefhydGWo= -github.com/ameshkov/dnscrypt v1.0.1 h1:Aoy/Sqiqk1b/AlBwdLb31QFUi+O02gzB+wDjhdePie0= -github.com/ameshkov/dnscrypt v1.0.1/go.mod h1:fEeZ+/h8DTt4FxEv9sxN61ygy/8m/vFRqRJcNGJR+r0= +github.com/ameshkov/dnscrypt v1.0.4 h1:vtwHm5m4R2dhcCx23wiI+gNBoy7qm4h7+kZ4Pucw/vE= +github.com/ameshkov/dnscrypt v1.0.4/go.mod h1:hVW52S6r0QvUpIwsyfZ1ifYYpfGu5pewD3pl7afMJcQ= +github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug= +github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I= github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= github.com/bluele/gcache v0.0.0-20171010155617-472614239ac7 h1:NpQ+gkFOH27AyDypSCJ/LdsIi/b4rdnEb1N5+IpFfYs= @@ -35,8 +29,6 @@ github.com/gobuffalo/packr v1.19.0/go.mod h1:MstrNkfCQhd5o+Ct4IJ0skWlxN8emOq8Dso github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4 h1:FMAReGTEDNr4AdbScv/PqzjMQUpkkVHiF/t8sDHQQVQ= github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4/go.mod h1:H6Ev6svFxUVPFThxLtdnFfcE9e3GWufpfmcVFpqV6HM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86 h1:Olj4M6T1omUfx7yTTcnhLf4xo6gYMmRHSJIfeA1NZy0= -github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86/go.mod h1:j/ONpSHHmPgDwmFKXg9vhQvIjADe/ft1X4a3TVOmp9g= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff h1:6NvhExg4omUC9NfA+l4Oq3ibNNeJUdiAF3iBVB0PlDk= @@ -45,8 +37,6 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joomcode/errorx v0.1.0 h1:QmJMiI1DE1UFje2aI1ZWO/VMT5a32qBoXUclGOt8vsc= github.com/joomcode/errorx v0.1.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32BM74ph7bPgqMztWopMYKgVyEho= github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o= github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc= @@ -63,39 +53,31 @@ github.com/shirou/gopsutil v2.18.10+incompatible h1:cy84jW6EVRPa5g9HAHrlbxMSIjBh github.com/shirou/gopsutil v2.18.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= go.uber.org/goleak v0.10.0 h1:G3eWbSNIskeRqtsN/1uI5B+eP73y3JUuBsv9AZjehb4= go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6 h1:gT0Y6H7hbVPUtvtk0YGxMXPgN+p8fYlqWkgJeUCZcaQ= golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4= -golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM= golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181217223516-dcdaa6325bcb h1:zzdd4xkMwu/GRxhSUJaCPh4/jil9kAbsU7AUmXboO+A= -golang.org/x/sys v0.0.0-20181217223516-dcdaa6325bcb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 h1:IcgEB62HYgAhX0Nd/QrVgZlxlcyxbGQHElLUhW2X4Fo= -golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb h1:pf3XwC90UUdNPYWZdFjhGBE7DUFuK3Ct1zWmZ65QN30= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477 h1:5xUJw+lg4zao9W4HIDzlFbMYgSgtvNVHh00MEHvbGpQ= gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477/go.mod h1:QDV1vrFSrowdoOba0UM8VJPUZONT7dnfdLsM+GG53Z8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From b0c4d88d5454f8dd5a92a73615cce3a31450f56b Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Sat, 29 Dec 2018 20:00:20 +0300 Subject: [PATCH 45/45] Indicate that DHCP is experimental --- client/src/__locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index fac7ceaa..b248ed6c 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -3,7 +3,7 @@ "save_config": "Save config", "enabled_dhcp": "DHCP server enabled", "disabled_dhcp": "DHCP server disabled", - "dhcp_title": "DHCP server", + "dhcp_title": "DHCP server (experimental!)", "dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.", "dhcp_enable": "Enable DHCP server", "dhcp_disable": "Disable DHCP server",