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>settings0>",
"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> <0>Null IP – Respond with zero IP address (0.0.0.0 for A; :: for AAAA);0> <0>Custom IP - Respond with a manually set IP address.0>",
+ "upstream_dns_client_desc": "If you keep this field empty, AdGuard Home will use the servers configured in the <0>DNS settings0>.",
"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,
+}) => (
+
+
+
+);
+
+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"