From 26ccee47b5045261907d65129fa1dea250efaf02 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 8 Nov 2019 14:59:12 +0300 Subject: [PATCH 1/8] + DNS: Get/Set DNS general settings GET /control/dns_info POST /control/dns_config --- AGHTechDoc.md | 39 +++++++++++++++++++++++ dnsforward/dnsforward.go | 4 +-- dnsforward/dnsforward_http.go | 59 ++++++++++++++++++++++++++++++++++- 3 files changed, 99 insertions(+), 3 deletions(-) diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 58a39dc6..4ac88137 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -29,6 +29,9 @@ Contents: * Static IP check/set * Add a static lease * API: Reset DHCP configuration +* DNS general settings + * API: Get DNS general settings + * API: Set DNS general settings * DNS access settings * List access settings * Set access settings @@ -801,6 +804,42 @@ Response: ] +## DNS general settings + +### API: Get DNS general settings + +Request: + + GET /control/dns_info + +Response: + + 200 OK + + { + "protection_enabled": true | false, + "ratelimit": 1234, + "blocking_mode": "nxdomain" | "null_ip", + } + + +### API: Set DNS general settings + +Request: + + POST /control/dns_config + + { + "protection_enabled": true | false, + "ratelimit": 1234, + "blocking_mode": "nxdomain" | "null_ip", + } + +Response: + + 200 OK + + ## DNS access settings There are low-level settings that can block undesired DNS requests. "Blocking" means not responding to request. diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index a3dabc01..e4947229 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -101,7 +101,7 @@ type FilteringConfig struct { BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600) - Ratelimit int `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable) + Ratelimit uint32 `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable) RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` // a list of whitelisted client IP addresses RefuseAny bool `yaml:"refuse_any"` // if true, refuse ANY requests BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only) @@ -214,7 +214,7 @@ func (s *Server) prepare(config *ServerConfig) error { proxyConfig := proxy.Config{ UDPListenAddr: s.conf.UDPListenAddr, TCPListenAddr: s.conf.TCPListenAddr, - Ratelimit: s.conf.Ratelimit, + Ratelimit: int(s.conf.Ratelimit), RatelimitWhitelist: s.conf.RatelimitWhitelist, RefuseAny: s.conf.RefuseAny, CacheEnabled: true, diff --git a/dnsforward/dnsforward_http.go b/dnsforward/dnsforward_http.go index 3941aba1..b284d1c2 100644 --- a/dnsforward/dnsforward_http.go +++ b/dnsforward/dnsforward_http.go @@ -20,6 +20,62 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string, http.Error(w, text, code) } +type dnsConfigJSON struct { + ProtectionEnabled bool `json:"protection_enabled"` + RateLimit uint32 `json:"ratelimit"` + BlockingMode string `json:"blocking_mode"` +} + +func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { + resp := dnsConfigJSON{} + s.RLock() + resp.ProtectionEnabled = s.conf.ProtectionEnabled + resp.BlockingMode = s.conf.BlockingMode + resp.RateLimit = s.conf.Ratelimit + s.RUnlock() + + js, err := json.Marshal(resp) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "json.Marshal: %s", err) + return + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(js) +} + +func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { + req := dnsConfigJSON{} + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) + return + } + + if !(req.BlockingMode == "nxdomain" || req.BlockingMode == "null_ip") { + httpError(r, w, http.StatusBadRequest, "blocking_mode: value not supported") + return + } + + restart := false + s.Lock() + s.conf.ProtectionEnabled = req.ProtectionEnabled + s.conf.BlockingMode = req.BlockingMode + if s.conf.Ratelimit != req.RateLimit { + restart = true + } + s.conf.Ratelimit = req.RateLimit + s.Unlock() + s.conf.ConfigModified() + + if restart { + err = s.Restart() + if err != nil { + httpError(r, w, http.StatusInternalServerError, "%s", err) + return + } + } +} + func (s *Server) handleProtectionEnable(w http.ResponseWriter, r *http.Request) { s.conf.ProtectionEnabled = true s.conf.ConfigModified() @@ -270,6 +326,8 @@ func checkDNS(input string, bootstrap []string) error { } func (s *Server) registerHandlers() { + s.conf.HTTPRegister("GET", "/control/dns_info", s.handleGetConfig) + s.conf.HTTPRegister("POST", "/control/dns_config", s.handleSetConfig) s.conf.HTTPRegister("POST", "/control/enable_protection", s.handleProtectionEnable) s.conf.HTTPRegister("POST", "/control/disable_protection", s.handleProtectionDisable) s.conf.HTTPRegister("POST", "/control/set_upstreams_config", s.handleSetUpstreamConfig) @@ -277,5 +335,4 @@ func (s *Server) registerHandlers() { s.conf.HTTPRegister("GET", "/control/access/list", s.handleAccessList) s.conf.HTTPRegister("POST", "/control/access/set", s.handleAccessSet) - } From 4f4da3397c9638e5a6a2b2a4896f3f6c70f65b98 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 8 Nov 2019 16:19:56 +0300 Subject: [PATCH 2/8] + dns: support blocking_mode=custom_ip --- AGHTechDoc.md | 10 ++++-- dnsforward/dnsforward.go | 15 ++++++++- dnsforward/dnsforward_http.go | 62 ++++++++++++++++++++++++++++++----- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 4ac88137..0aade0a7 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -819,7 +819,9 @@ Response: { "protection_enabled": true | false, "ratelimit": 1234, - "blocking_mode": "nxdomain" | "null_ip", + "blocking_mode": "nxdomain" | "null_ip" | "custom_ip", + "blocking_ipv4": "1.2.3.4", + "blocking_ipv6": "1:2:3::4", } @@ -832,13 +834,17 @@ Request: { "protection_enabled": true | false, "ratelimit": 1234, - "blocking_mode": "nxdomain" | "null_ip", + "blocking_mode": "nxdomain" | "null_ip" | "custom_ip", + "blocking_ipv4": "1.2.3.4", + "blocking_ipv6": "1:2:3::4", } Response: 200 OK +`blocking_ipv4` and `blocking_ipv6` values are active when `blocking_mode` is set to `custom_ip`. + ## DNS access settings diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index e4947229..2630770e 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -99,7 +99,12 @@ type FilteringConfig struct { ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features - BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests + BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests + BlockingIPv4 string `yaml:"blocking_ipv4"` // IP address to be returned for a blocked A request + BlockingIPv6 string `yaml:"blocking_ipv6"` // IP address to be returned for a blocked AAAA request + BlockingIPAddrv4 net.IP `yaml:"-"` + BlockingIPAddrv6 net.IP `yaml:"-"` + BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600) Ratelimit uint32 `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable) RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` // a list of whitelisted client IP addresses @@ -657,6 +662,14 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu case dns.TypeAAAA: return s.genAAAARecord(m, net.IPv6zero) } + + } else if s.conf.BlockingMode == "custom_ip" { + switch m.Question[0].Qtype { + case dns.TypeA: + return s.genARecord(m, s.conf.BlockingIPAddrv4) + case dns.TypeAAAA: + return s.genAAAARecord(m, s.conf.BlockingIPAddrv6) + } } return s.genNXDomain(m) diff --git a/dnsforward/dnsforward_http.go b/dnsforward/dnsforward_http.go index b284d1c2..a2660b81 100644 --- a/dnsforward/dnsforward_http.go +++ b/dnsforward/dnsforward_http.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/AdguardTeam/golibs/jsonutil" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/utils" "github.com/miekg/dns" @@ -24,6 +25,8 @@ type dnsConfigJSON struct { ProtectionEnabled bool `json:"protection_enabled"` RateLimit uint32 `json:"ratelimit"` BlockingMode string `json:"blocking_mode"` + BlockingIPv4 string `json:"blocking_ipv4"` + BlockingIPv6 string `json:"blocking_ipv6"` } func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { @@ -31,6 +34,8 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { s.RLock() resp.ProtectionEnabled = s.conf.ProtectionEnabled resp.BlockingMode = s.conf.BlockingMode + resp.BlockingIPv4 = s.conf.BlockingIPv4 + resp.BlockingIPv6 = s.conf.BlockingIPv6 resp.RateLimit = s.conf.Ratelimit s.RUnlock() @@ -43,27 +48,68 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { _, _ = w.Write(js) } +func checkBlockingMode(req dnsConfigJSON) bool { + bm := req.BlockingMode + if !(bm == "nxdomain" || bm == "null_ip" || bm == "custom_ip") { + return false + } + + if bm == "custom_ip" { + ip := net.ParseIP(req.BlockingIPv4) + if ip == nil || ip.To4() == nil { + return false + } + + ip = net.ParseIP(req.BlockingIPv6) + if ip == nil { + return false + } + } + + return true +} + func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { req := dnsConfigJSON{} - err := json.NewDecoder(r.Body).Decode(&req) + js, err := jsonutil.DecodeObject(&req, r.Body) if err != nil { httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) return } - if !(req.BlockingMode == "nxdomain" || req.BlockingMode == "null_ip") { - httpError(r, w, http.StatusBadRequest, "blocking_mode: value not supported") + if js.Exists("blocking_mode") && !checkBlockingMode(req) { + httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value") return } restart := false s.Lock() - s.conf.ProtectionEnabled = req.ProtectionEnabled - s.conf.BlockingMode = req.BlockingMode - if s.conf.Ratelimit != req.RateLimit { - restart = true + + if js.Exists("protection_enabled") { + s.conf.ProtectionEnabled = req.ProtectionEnabled } - s.conf.Ratelimit = req.RateLimit + + if js.Exists("blocking_mode") { + s.conf.BlockingMode = req.BlockingMode + if req.BlockingMode == "custom_ip" { + if js.Exists("blocking_ipv4") { + s.conf.BlockingIPv4 = req.BlockingIPv4 + s.conf.BlockingIPAddrv4 = net.ParseIP(req.BlockingIPv4) + } + if js.Exists("blocking_ipv6") { + s.conf.BlockingIPv6 = req.BlockingIPv6 + s.conf.BlockingIPAddrv6 = net.ParseIP(req.BlockingIPv6) + } + } + } + + if js.Exists("ratelimit") { + if s.conf.Ratelimit != req.RateLimit { + restart = true + } + s.conf.Ratelimit = req.RateLimit + } + s.Unlock() s.conf.ConfigModified() From 1b3122dd35c23a4cc4dbd86be842f3b97d93ff48 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 12 Nov 2019 18:26:45 +0300 Subject: [PATCH 3/8] * /control/set_upstreams_config: allow empty upstream list --- dnsforward/dnsforward_http.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dnsforward/dnsforward_http.go b/dnsforward/dnsforward_http.go index a2660b81..25a2aae3 100644 --- a/dnsforward/dnsforward_http.go +++ b/dnsforward/dnsforward_http.go @@ -146,10 +146,12 @@ func (s *Server) handleSetUpstreamConfig(w http.ResponseWriter, r *http.Request) return } - err = ValidateUpstreams(req.Upstreams) - if err != nil { - httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err) - return + if len(req.Upstreams) != 0 { + err = ValidateUpstreams(req.Upstreams) + if err != nil { + httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err) + return + } } newconf := FilteringConfig{} From 3b13c031a3948952f546e20c069db6e3f375a3d2 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Thu, 14 Nov 2019 16:36:59 +0300 Subject: [PATCH 4/8] + openapi: /dns_info, /dns_config --- openapi/openapi.yaml | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index ea6281d5..f8b9bdb8 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -70,6 +70,35 @@ paths: schema: $ref: "#/definitions/ServerStatus" + /dns_info: + get: + tags: + - global + operationId: dnsInfo + summary: 'Get general DNS parameters' + responses: + 200: + description: OK + schema: + $ref: "#/definitions/DNSConfig" + + /dns_config: + post: + tags: + - global + operationId: dnsConfig + summary: "Set general DNS parameters" + consumes: + - application/json + parameters: + - in: "body" + name: "body" + schema: + $ref: "#/definitions/DNSConfig" + responses: + 200: + description: OK + /enable_protection: post: tags: @@ -1054,6 +1083,26 @@ definitions: language: type: "string" example: "en" + + DNSConfig: + type: "object" + description: "Query log configuration" + properties: + protection_enabled: + type: "boolean" + ratelimit: + type: "integer" + blocking_mode: + type: "string" + enum: + - "nxdomain" + - "null_ip" + - "custom_ip" + blocking_ipv4: + type: "string" + blocking_ipv6: + type: "string" + UpstreamsConfig: type: "object" description: "Upstreams configuration" From 87bb773d3e087c1d0b21ce7e98045091fd99e105 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 15 Nov 2019 11:36:47 +0300 Subject: [PATCH 5/8] * DNS: remove /enable_protection and /disable_protection --- dnsforward/dnsforward_http.go | 12 ------------ openapi/CHANGELOG.md | 24 ++++++++++++++++++++++++ openapi/openapi.yaml | 20 -------------------- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/dnsforward/dnsforward_http.go b/dnsforward/dnsforward_http.go index 25a2aae3..680f5c2b 100644 --- a/dnsforward/dnsforward_http.go +++ b/dnsforward/dnsforward_http.go @@ -122,16 +122,6 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { } } -func (s *Server) handleProtectionEnable(w http.ResponseWriter, r *http.Request) { - s.conf.ProtectionEnabled = true - s.conf.ConfigModified() -} - -func (s *Server) handleProtectionDisable(w http.ResponseWriter, r *http.Request) { - s.conf.ProtectionEnabled = false - s.conf.ConfigModified() -} - type upstreamJSON struct { Upstreams []string `json:"upstream_dns"` // Upstreams BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS @@ -376,8 +366,6 @@ func checkDNS(input string, bootstrap []string) error { func (s *Server) registerHandlers() { s.conf.HTTPRegister("GET", "/control/dns_info", s.handleGetConfig) s.conf.HTTPRegister("POST", "/control/dns_config", s.handleSetConfig) - s.conf.HTTPRegister("POST", "/control/enable_protection", s.handleProtectionEnable) - s.conf.HTTPRegister("POST", "/control/disable_protection", s.handleProtectionDisable) s.conf.HTTPRegister("POST", "/control/set_upstreams_config", s.handleSetUpstreamConfig) s.conf.HTTPRegister("POST", "/control/test_upstream_dns", s.handleTestUpstreamDNS) diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index c6e79b8a..d611cb27 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -147,6 +147,30 @@ Response: } +### Set DNS general settings: POST /control/dns_config + +Replaces these API methods: + + POST /control/enable_protection + POST /control/disable_protection + +Request: + + POST /control/dns_config + + { + "protection_enabled": true | false, + "ratelimit": 1234, + "blocking_mode": "nxdomain" | "null_ip" | "custom_ip", + "blocking_ipv4": "1.2.3.4", + "blocking_ipv6": "1:2:3::4", + } + +Response: + + 200 OK + + ## v0.99: incompatible API changes * A note about web user authentication diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index f8b9bdb8..410e668d 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -99,26 +99,6 @@ paths: 200: description: OK - /enable_protection: - post: - tags: - - global - operationId: enableProtection - summary: "Enable protection (turns on dnsfilter module in coredns)" - responses: - 200: - description: OK - - /disable_protection: - post: - tags: - - global - operationId: disableProtection - summary: "Disable protection (turns off filtering, sb, parental, safesearch temporarily by disabling dnsfilter module in coredns)" - responses: - 200: - description: OK - /set_upstreams_config: post: tags: From 197d07f32ba242e13939e01c6511231b0b6abc5a Mon Sep 17 00:00:00 2001 From: Artem Baskal Date: Wed, 4 Dec 2019 21:52:38 +0300 Subject: [PATCH 6/8] + client: handle DNS config --- client/src/__locales/en.json | 9 ++ client/src/actions/dnsConfig.js | 35 +++++ client/src/actions/index.js | 12 +- client/src/api/Api.js | 30 +++-- .../src/components/Settings/DnsConfig/Form.js | 120 ++++++++++++++++++ .../components/Settings/DnsConfig/index.js | 49 +++++++ client/src/components/Settings/index.js | 12 ++ client/src/containers/Settings.js | 6 +- client/src/helpers/constants.js | 6 + client/src/reducers/dnsConfig.js | 36 ++++++ client/src/reducers/index.js | 2 + 11 files changed, 294 insertions(+), 23 deletions(-) create mode 100644 client/src/actions/dnsConfig.js create mode 100644 client/src/components/Settings/DnsConfig/Form.js create mode 100644 client/src/components/Settings/DnsConfig/index.js create mode 100644 client/src/reducers/dnsConfig.js diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 14f353aa..44a64bea 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -187,6 +187,15 @@ "query_log_disabled": "The query log is disabled and can be configured in the <0>settings", "query_log_strict_search": "Use double quotes for strict search", "query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost", + "dns_config": "DNS configuration", + "blocking_mode": "Blocking mode", + "nxdomain": "NXDOMAIN", + "null_ip": "Null IP", + "custom_ip": "Custom IP", + "blocking_ipv4": "Blocking IPv4", + "blocking_ipv6": "Blocking IPv6", + "form_enter_rate_limit": "Enter rate limit", + "rate_limit": "Rate limit", "source_label": "Source", "found_in_known_domain_db": "Found in the known domains database.", "category_label": "Category", diff --git a/client/src/actions/dnsConfig.js b/client/src/actions/dnsConfig.js new file mode 100644 index 00000000..1976613e --- /dev/null +++ b/client/src/actions/dnsConfig.js @@ -0,0 +1,35 @@ +import { createAction } from 'redux-actions'; + +import apiClient from '../api/Api'; +import { addErrorToast, addSuccessToast } from './index'; + +export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST'); +export const getDnsConfigFailure = createAction('GET_DNS_CONFIG_FAILURE'); +export const getDnsConfigSuccess = createAction('GET_DNS_CONFIG_SUCCESS'); + +export const getDnsConfig = () => async (dispatch) => { + dispatch(getDnsConfigRequest()); + try { + const data = await apiClient.getDnsConfig(); + dispatch(getDnsConfigSuccess(data)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getDnsConfigFailure()); + } +}; + +export const setDnsConfigRequest = createAction('SET_DNS_CONFIG_REQUEST'); +export const setDnsConfigFailure = createAction('SET_DNS_CONFIG_FAILURE'); +export const setDnsConfigSuccess = createAction('SET_DNS_CONFIG_SUCCESS'); + +export const setDnsConfig = config => async (dispatch) => { + dispatch(setDnsConfigRequest()); + try { + await apiClient.setDnsConfig(config); + dispatch(addSuccessToast('config_successfully_saved')); + dispatch(setDnsConfigSuccess(config)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(setDnsConfigFailure()); + } +}; diff --git a/client/src/actions/index.js b/client/src/actions/index.js index d01130f6..4583c1ae 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -91,17 +91,9 @@ export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS') export const toggleProtection = status => async (dispatch) => { dispatch(toggleProtectionRequest()); - let successMessage = ''; - try { - if (status) { - successMessage = 'disabled_protection'; - await apiClient.disableGlobalProtection(); - } else { - successMessage = 'enabled_protection'; - await apiClient.enableGlobalProtection(); - } - + const successMessage = status ? 'disabled_protection' : 'enabled_protection'; + await apiClient.setDnsConfig({ protection_enabled: !status }); dispatch(addSuccessToast(successMessage)); dispatch(toggleProtectionSuccess()); } catch (error) { diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 2393a89d..77dda64b 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -30,8 +30,6 @@ class Api { GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstreams_config', method: 'POST' }; GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' }; GLOBAL_VERSION = { path: 'version.json', method: 'POST' }; - GLOBAL_ENABLE_PROTECTION = { path: 'enable_protection', method: 'POST' }; - GLOBAL_DISABLE_PROTECTION = { path: 'disable_protection', method: 'POST' }; GLOBAL_UPDATE = { path: 'update', method: 'POST' }; startGlobalFiltering() { @@ -76,16 +74,6 @@ class Api { return this.makeRequest(path, method, config); } - enableGlobalProtection() { - const { path, method } = this.GLOBAL_ENABLE_PROTECTION; - return this.makeRequest(path, method); - } - - disableGlobalProtection() { - const { path, method } = this.GLOBAL_DISABLE_PROTECTION; - return this.makeRequest(path, method); - } - getUpdate() { const { path, method } = this.GLOBAL_UPDATE; return this.makeRequest(path, method); @@ -546,6 +534,24 @@ class Api { const { path, method } = this.GET_PROFILE; return this.makeRequest(path, method); } + + // DNS config + GET_DNS_CONFIG = { path: 'dns_info', method: 'GET' }; + SET_DNS_CONFIG = { path: 'dns_config', method: 'POST' }; + + getDnsConfig() { + const { path, method } = this.GET_DNS_CONFIG; + return this.makeRequest(path, method); + } + + setDnsConfig(data) { + const { path, method } = this.SET_DNS_CONFIG; + const config = { + data, + headers: { 'Content-Type': 'application/json' }, + }; + return this.makeRequest(path, method, config); + } } const apiClient = new Api(); diff --git a/client/src/components/Settings/DnsConfig/Form.js b/client/src/components/Settings/DnsConfig/Form.js new file mode 100644 index 00000000..4cfdff35 --- /dev/null +++ b/client/src/components/Settings/DnsConfig/Form.js @@ -0,0 +1,120 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { Field, reduxForm, formValueSelector } from 'redux-form'; +import { Trans, withNamespaces } from 'react-i18next'; +import flow from 'lodash/flow'; + +import { renderField, renderRadioField, required, ipv4, ipv6, isPositive, toNumber } from '../../../helpers/form'; +import { BLOCKING_MODES } from '../../../helpers/constants'; + +const getFields = (processing, t) => Object.values(BLOCKING_MODES).map(mode => ( + +)); + +let Form = ({ + handleSubmit, submitting, invalid, processing, blockingMode, t, +}) => ( +
+
+
+
+ + +
+
+
+
+ +
+ {getFields(processing, t)} +
+
+
+ {blockingMode === BLOCKING_MODES.custom_ip && ( + +
+
+ + +
+
+
+
+ + +
+
+
+ )} +
+ +
+); + +Form.propTypes = { + blockingMode: PropTypes.string.isRequired, + handleSubmit: PropTypes.func.isRequired, + submitting: PropTypes.bool.isRequired, + invalid: PropTypes.bool.isRequired, + processing: PropTypes.bool.isRequired, + t: PropTypes.func.isRequired, +}; + +const selector = formValueSelector('blockingModeForm'); + +Form = connect((state) => { + const blockingMode = selector(state, 'blocking_mode'); + return { + blockingMode, + }; +})(Form); + +export default flow([ + withNamespaces(), + reduxForm({ + form: 'blockingModeForm', + }), +])(Form); diff --git a/client/src/components/Settings/DnsConfig/index.js b/client/src/components/Settings/DnsConfig/index.js new file mode 100644 index 00000000..c0f0fca0 --- /dev/null +++ b/client/src/components/Settings/DnsConfig/index.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withNamespaces } from 'react-i18next'; + +import Card from '../../ui/Card'; +import Form from './Form'; + +const DnsConfig = ({ t, dnsConfig, setDnsConfig }) => { + const handleFormSubmit = (values) => { + setDnsConfig(values); + }; + + const { + blocking_mode, + ratelimit, + blocking_ipv4, + blocking_ipv6, + processingSetConfig, + } = dnsConfig; + + return ( + +
+
+
+
+ ); +}; + +DnsConfig.propTypes = { + dnsConfig: PropTypes.object.isRequired, + setDnsConfig: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, +}; + +export default withNamespaces()(DnsConfig); diff --git a/client/src/components/Settings/index.js b/client/src/components/Settings/index.js index 9c05f680..5fcc72cd 100644 --- a/client/src/components/Settings/index.js +++ b/client/src/components/Settings/index.js @@ -6,6 +6,8 @@ import Services from './Services'; import StatsConfig from './StatsConfig'; import LogsConfig from './LogsConfig'; import FiltersConfig from './FiltersConfig'; +import DnsConfig from './DnsConfig'; + import Checkbox from '../ui/Checkbox'; import Loading from '../ui/Loading'; import PageTitle from '../ui/PageTitle'; @@ -38,6 +40,7 @@ class Settings extends Component { this.props.getStatsConfig(); this.props.getLogsConfig(); this.props.getFilteringStatus(); + this.props.getDnsConfig(); } renderSettings = (settings) => { @@ -68,10 +71,12 @@ class Settings extends Component { resetStats, stats, queryLogs, + dnsConfig, setLogsConfig, clearLogs, filtering, setFiltersConfig, + setDnsConfig, t, } = this.props; @@ -101,6 +106,12 @@ class Settings extends Component { +
+ +
{ const { - settings, services, stats, queryLogs, filtering, + settings, services, stats, queryLogs, filtering, dnsConfig, } = state; const props = { settings, @@ -16,6 +17,7 @@ const mapStateToProps = (state) => { stats, queryLogs, filtering, + dnsConfig, }; return props; }; @@ -33,6 +35,8 @@ const mapDispatchToProps = { setLogsConfig, getFilteringStatus, setFiltersConfig, + getDnsConfig, + setDnsConfig, }; export default connect( diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index fefd9425..30076306 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -341,6 +341,12 @@ export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90]; export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168]; +export const BLOCKING_MODES = { + nxdomain: 'nxdomain', + null_ip: 'null_ip', + custom_ip: 'custom_ip', +}; + export const WHOIS_ICONS = { location: 'location', orgname: 'network', diff --git a/client/src/reducers/dnsConfig.js b/client/src/reducers/dnsConfig.js new file mode 100644 index 00000000..c2807261 --- /dev/null +++ b/client/src/reducers/dnsConfig.js @@ -0,0 +1,36 @@ +import { handleActions } from 'redux-actions'; + +import * as actions from '../actions/dnsConfig'; +import { BLOCKING_MODES } from '../helpers/constants'; + +const dnsConfig = handleActions( + { + [actions.getDnsConfigRequest]: state => ({ ...state, processingGetConfig: true }), + [actions.getDnsConfigFailure]: state => + ({ ...state, processingGetConfig: false }), + [actions.getDnsConfigSuccess]: (state, { payload }) => ({ + ...state, + ...payload, + processingGetConfig: false, + }), + + [actions.setDnsConfigRequest]: state => ({ ...state, processingSetConfig: true }), + [actions.setDnsConfigFailure]: state => + ({ ...state, processingSetConfig: false }), + [actions.setDnsConfigSuccess]: (state, { payload }) => ({ + ...state, + ...payload, + processingSetConfig: false, + }), + }, + { + processingGetConfig: false, + processingSetConfig: false, + blocking_mode: BLOCKING_MODES.nxdomain, + ratelimit: 20, + blocking_ipv4: '', + blocking_ipv6: '', + }, +); + +export default dnsConfig; diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 3eca36a6..0e2b69bb 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -13,6 +13,7 @@ import rewrites from './rewrites'; import services from './services'; import stats from './stats'; import queryLogs from './queryLogs'; +import dnsConfig from './dnsConfig'; import filtering from './filtering'; const settings = handleActions( @@ -369,6 +370,7 @@ export default combineReducers({ rewrites, services, stats, + dnsConfig, loadingBar: loadingBarReducer, form: formReducer, }); From 19a94bf789bba6ab1b831e956600428998822de1 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 12 Nov 2019 19:45:55 +0300 Subject: [PATCH 7/8] + dns: add "edns_client_subnet" setting --- AGHTechDoc.md | 2 ++ dnsforward/dnsforward.go | 3 +++ dnsforward/dnsforward_http.go | 7 +++++++ openapi/openapi.yaml | 2 ++ 4 files changed, 14 insertions(+) diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 0aade0a7..ea2ed95a 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -822,6 +822,7 @@ Response: "blocking_mode": "nxdomain" | "null_ip" | "custom_ip", "blocking_ipv4": "1.2.3.4", "blocking_ipv6": "1:2:3::4", + "edns_cs_enabled": true | false, } @@ -837,6 +838,7 @@ Request: "blocking_mode": "nxdomain" | "null_ip" | "custom_ip", "blocking_ipv4": "1.2.3.4", "blocking_ipv6": "1:2:3::4", + "edns_cs_enabled": true | false, } Response: diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index 2630770e..afd0149e 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -112,6 +112,8 @@ type FilteringConfig struct { BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only) AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled + EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option + AllowedClients []string `yaml:"allowed_clients"` // IP addresses of whitelist clients DisallowedClients []string `yaml:"disallowed_clients"` // IP addresses of clients that should be blocked BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked @@ -229,6 +231,7 @@ func (s *Server) prepare(config *ServerConfig) error { BeforeRequestHandler: s.beforeRequestHandler, RequestHandler: s.handleDNSRequest, AllServers: s.conf.AllServers, + EnableEDNSClientSubnet: s.conf.EnableEDNSClientSubnet, } s.access = &accessCtx{} diff --git a/dnsforward/dnsforward_http.go b/dnsforward/dnsforward_http.go index 680f5c2b..e0ddd36b 100644 --- a/dnsforward/dnsforward_http.go +++ b/dnsforward/dnsforward_http.go @@ -27,6 +27,7 @@ type dnsConfigJSON struct { BlockingMode string `json:"blocking_mode"` BlockingIPv4 string `json:"blocking_ipv4"` BlockingIPv6 string `json:"blocking_ipv6"` + EDNSCSEnabled bool `json:"edns_cs_enabled"` } func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { @@ -37,6 +38,7 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { resp.BlockingIPv4 = s.conf.BlockingIPv4 resp.BlockingIPv6 = s.conf.BlockingIPv6 resp.RateLimit = s.conf.Ratelimit + resp.EDNSCSEnabled = s.conf.EnableEDNSClientSubnet s.RUnlock() js, err := json.Marshal(resp) @@ -110,6 +112,11 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { s.conf.Ratelimit = req.RateLimit } + if js.Exists("edns_cs_enabled") { + s.conf.EnableEDNSClientSubnet = req.EDNSCSEnabled + restart = true + } + s.Unlock() s.conf.ConfigModified() diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 410e668d..2400b9b7 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1082,6 +1082,8 @@ definitions: type: "string" blocking_ipv6: type: "string" + edns_cs_enabled: + type: "boolean" UpstreamsConfig: type: "object" From 97e77cab643d6784067ce97c0f03ec3e4612c2c9 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Thu, 5 Dec 2019 16:00:20 +0300 Subject: [PATCH 8/8] + client: handle EDNS Client Subnet setting --- client/src/__locales/en.json | 10 +++- .../src/components/Settings/Clients/Form.js | 5 ++ .../{DnsConfig => Dns/Config}/Form.js | 51 +++++++++++++++---- .../{DnsConfig => Dns/Config}/index.js | 10 ++-- client/src/components/Settings/Dns/index.js | 23 +++++++-- client/src/components/Settings/index.js | 11 ---- client/src/containers/Dns.js | 6 ++- client/src/containers/Settings.js | 6 +-- client/src/helpers/form.js | 7 +++ client/src/reducers/dnsConfig.js | 28 +++++++--- 10 files changed, 115 insertions(+), 42 deletions(-) rename client/src/components/Settings/{DnsConfig => Dns/Config}/Form.js (67%) rename client/src/components/Settings/{DnsConfig => Dns/Config}/index.js (83%) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 44a64bea..bb83a92d 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -24,6 +24,7 @@ "form_error_ip_format": "Invalid IP format", "form_error_mac_format": "Invalid MAC format", "form_error_positive": "Must be greater than 0", + "form_error_negative": "Must be equal to 0 or greater", "dhcp_form_gateway_input": "Gateway IP", "dhcp_form_subnet_input": "Subnet mask", "dhcp_form_range_title": "Range of IP addresses", @@ -187,7 +188,7 @@ "query_log_disabled": "The query log is disabled and can be configured in the <0>settings", "query_log_strict_search": "Use double quotes for strict search", "query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost", - "dns_config": "DNS configuration", + "dns_config": "DNS server configuration", "blocking_mode": "Blocking mode", "nxdomain": "NXDOMAIN", "null_ip": "Null IP", @@ -196,6 +197,13 @@ "blocking_ipv6": "Blocking IPv6", "form_enter_rate_limit": "Enter rate limit", "rate_limit": "Rate limit", + "edns_enable": "Enable EDNS Client Subnet", + "edns_cs_desc": "If enabled, AdGuard Home will be sending clients' subnets to the DNS servers.", + "rate_limit_desc": "The number of requests per second that a single client is allowed to make (0: unlimited)", + "blocking_ipv4_desc": "IP address to be returned for a blocked A request", + "blocking_ipv6_desc": "IP address to be returned for a blocked AAAA request", + "blocking_mode_desc": "<0>NXDOMAIN – Respond with NXDOMAIN code; <0>Null IP – Respond with zero IP address (0.0.0.0 for A; :: for AAAA); <0>Custom IP - Respond with a manually set IP address.", + "upstream_dns_client_desc": "If you keep this field empty, AdGuard Home will use the servers configured in the <0>DNS settings.", "source_label": "Source", "found_in_known_domain_db": "Found in the known domains database.", "category_label": "Category", diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js index 63199d76..897e1fc5 100644 --- a/client/src/components/Settings/Clients/Form.js +++ b/client/src/components/Settings/Clients/Form.js @@ -225,6 +225,11 @@ let Form = (props) => {
+
+ link]}> + upstream_dns_client_desc + +
Object.values(BLOCKING_MODES).map(mode => (
- +
+ rate_limit_desc +
-
-
+
+
+ +
+ text
]}>blocking_mode_desc +
{getFields(processing, t)}
@@ -55,9 +82,12 @@ let Form = ({
-