diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 58a39dc6..ea2ed95a 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,50 @@ 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" | "custom_ip", + "blocking_ipv4": "1.2.3.4", + "blocking_ipv6": "1:2:3::4", + "edns_cs_enabled": true | false, + } + + +### API: Set DNS general settings + +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", + "edns_cs_enabled": true | false, + } + +Response: + + 200 OK + +`blocking_ipv4` and `blocking_ipv6` values are active when `blocking_mode` is set to `custom_ip`. + + ## DNS access settings There are low-level settings that can block undesired DNS requests. "Blocking" means not responding to request. diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 14f353aa..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,6 +188,22 @@ "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 server 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", + "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/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/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 => ( + +)); + +let Form = ({ + handleSubmit, submitting, invalid, processing, blockingMode, t, +}) => ( +
+
+
+
+ +
+ rate_limit_desc +
+ +
+
+
+
+ +
+
+
+
+ +
+ text
]}>blocking_mode_desc +
+
+ {getFields(processing, t)} +
+
+
+ {blockingMode === BLOCKING_MODES.custom_ip && ( + +
+
+ +
+ blocking_ipv4_desc +
+ +
+
+
+
+ +
+ blocking_ipv6_desc +
+ +
+
+
+ )} +
+ + +); + +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/Dns/Config/index.js b/client/src/components/Settings/Dns/Config/index.js new file mode 100644 index 00000000..6404a416 --- /dev/null +++ b/client/src/components/Settings/Dns/Config/index.js @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withNamespaces } from 'react-i18next'; + +import Card from '../../../ui/Card'; +import Form from './Form'; + +const Config = ({ t, dnsConfig, setDnsConfig }) => { + const handleFormSubmit = (values) => { + setDnsConfig(values); + }; + + const { + blocking_mode, + ratelimit, + blocking_ipv4, + blocking_ipv6, + edns_cs_enabled, + processingSetConfig, + } = dnsConfig; + + return ( + +
+
+
+
+ ); +}; + +Config.propTypes = { + dnsConfig: PropTypes.object.isRequired, + setDnsConfig: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, +}; + +export default withNamespaces()(Config); diff --git a/client/src/components/Settings/Dns/index.js b/client/src/components/Settings/Dns/index.js index f64b3f3c..77d27b58 100644 --- a/client/src/components/Settings/Dns/index.js +++ b/client/src/components/Settings/Dns/index.js @@ -5,6 +5,7 @@ import { withNamespaces } from 'react-i18next'; import Upstream from './Upstream'; import Access from './Access'; import Rewrites from './Rewrites'; +import Config from './Config'; import PageTitle from '../../ui/PageTitle'; import Loading from '../../ui/Loading'; @@ -13,6 +14,7 @@ class Dns extends Component { this.props.getDnsSettings(); this.props.getAccessList(); this.props.getRewritesList(); + this.props.getDnsConfig(); } render() { @@ -29,12 +31,18 @@ class Dns extends Component { addRewrite, deleteRewrite, toggleRewritesModal, + dnsConfig, + setDnsConfig, } = this.props; - const isDataLoading = - dashboard.processingDnsSettings || access.processing || rewrites.processing; - const isDataReady = - !dashboard.processingDnsSettings && !access.processing && !rewrites.processing; + const isDataLoading = dashboard.processingDnsSettings + || access.processing + || rewrites.processing + || dnsConfig.processingGetConfig; + const isDataReady = !dashboard.processingDnsSettings + && !access.processing + && !rewrites.processing + && !dnsConfig.processingGetConfig; return ( @@ -42,6 +50,10 @@ class Dns extends Component { {isDataLoading && } {isDataReady && ( + { const { - dashboard, settings, access, rewrites, + dashboard, settings, access, rewrites, dnsConfig, } = state; const props = { dashboard, settings, access, rewrites, + dnsConfig, }; return props; }; @@ -33,6 +35,8 @@ const mapDispatchToProps = { deleteRewrite, toggleRewritesModal, getDnsSettings, + 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/helpers/form.js b/client/src/helpers/form.js index 55e0b0a0..283f0975 100644 --- a/client/src/helpers/form.js +++ b/client/src/helpers/form.js @@ -189,6 +189,13 @@ export const isPositive = (value) => { return false; }; +export const biggerOrEqualZero = (value) => { + if (value < 0) { + return form_error_negative; + } + return false; +}; + export const port = (value) => { if ((value || value === 0) && (value < 80 || value > 65535)) { return form_error_port_range; diff --git a/client/src/reducers/dnsConfig.js b/client/src/reducers/dnsConfig.js new file mode 100644 index 00000000..5c7c2b88 --- /dev/null +++ b/client/src/reducers/dnsConfig.js @@ -0,0 +1,50 @@ +import { handleActions } from 'redux-actions'; + +import * as actions from '../actions/dnsConfig'; +import { BLOCKING_MODES } from '../helpers/constants'; + +const DEFAULT_BLOCKING_IPV4 = '0.0.0.0'; +const DEFAULT_BLOCKING_IPV6 = '::'; + +const dnsConfig = handleActions( + { + [actions.getDnsConfigRequest]: state => ({ ...state, processingGetConfig: true }), + [actions.getDnsConfigFailure]: state => + ({ ...state, processingGetConfig: false }), + [actions.getDnsConfigSuccess]: (state, { payload }) => { + const { + blocking_ipv4, + blocking_ipv6, + ...values + } = payload; + + return { + ...state, + ...values, + blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4, + blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6, + 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: DEFAULT_BLOCKING_IPV4, + blocking_ipv6: DEFAULT_BLOCKING_IPV6, + edns_cs_enabled: false, + }, +); + +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, }); diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index a3dabc01..afd0149e 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -99,14 +99,21 @@ 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 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) 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 @@ -214,7 +221,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, @@ -224,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{} @@ -657,6 +665,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 3941aba1..e0ddd36b 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" @@ -20,14 +21,112 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string, http.Error(w, text, code) } -func (s *Server) handleProtectionEnable(w http.ResponseWriter, r *http.Request) { - s.conf.ProtectionEnabled = true - s.conf.ConfigModified() +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"` + EDNSCSEnabled bool `json:"edns_cs_enabled"` } -func (s *Server) handleProtectionDisable(w http.ResponseWriter, r *http.Request) { - s.conf.ProtectionEnabled = false +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.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) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "json.Marshal: %s", err) + return + } + w.Header().Set("Content-Type", "application/json") + _, _ = 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{} + js, err := jsonutil.DecodeObject(&req, r.Body) + if err != nil { + httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) + return + } + + if js.Exists("blocking_mode") && !checkBlockingMode(req) { + httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value") + return + } + + restart := false + s.Lock() + + if js.Exists("protection_enabled") { + s.conf.ProtectionEnabled = req.ProtectionEnabled + } + + 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 + } + + if js.Exists("edns_cs_enabled") { + s.conf.EnableEDNSClientSubnet = req.EDNSCSEnabled + restart = true + } + + s.Unlock() s.conf.ConfigModified() + + if restart { + err = s.Restart() + if err != nil { + httpError(r, w, http.StatusInternalServerError, "%s", err) + return + } + } } type upstreamJSON struct { @@ -44,10 +143,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{} @@ -270,12 +371,11 @@ func checkDNS(input string, bootstrap []string) error { } func (s *Server) registerHandlers() { - s.conf.HTTPRegister("POST", "/control/enable_protection", s.handleProtectionEnable) - s.conf.HTTPRegister("POST", "/control/disable_protection", s.handleProtectionDisable) + s.conf.HTTPRegister("GET", "/control/dns_info", s.handleGetConfig) + s.conf.HTTPRegister("POST", "/control/dns_config", s.handleSetConfig) s.conf.HTTPRegister("POST", "/control/set_upstreams_config", s.handleSetUpstreamConfig) s.conf.HTTPRegister("POST", "/control/test_upstream_dns", s.handleTestUpstreamDNS) s.conf.HTTPRegister("GET", "/control/access/list", s.handleAccessList) s.conf.HTTPRegister("POST", "/control/access/set", s.handleAccessSet) - } 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 ea6281d5..2400b9b7 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -70,22 +70,31 @@ paths: schema: $ref: "#/definitions/ServerStatus" - /enable_protection: - post: + /dns_info: + get: tags: - global - operationId: enableProtection - summary: "Enable protection (turns on dnsfilter module in coredns)" + operationId: dnsInfo + summary: 'Get general DNS parameters' responses: 200: description: OK + schema: + $ref: "#/definitions/DNSConfig" - /disable_protection: + /dns_config: post: tags: - global - operationId: disableProtection - summary: "Disable protection (turns off filtering, sb, parental, safesearch temporarily by disabling dnsfilter module in coredns)" + operationId: dnsConfig + summary: "Set general DNS parameters" + consumes: + - application/json + parameters: + - in: "body" + name: "body" + schema: + $ref: "#/definitions/DNSConfig" responses: 200: description: OK @@ -1054,6 +1063,28 @@ 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" + edns_cs_enabled: + type: "boolean" + UpstreamsConfig: type: "object" description: "Upstreams configuration"