diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 388691b0..efe77161 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -587,5 +587,11 @@
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction0> on how to resolve this.",
"adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.",
"client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.",
- "experimental": "Experimental"
+ "experimental": "Experimental",
+ "rebinding_title": "DNS Rebinding Protection",
+ "rebinding_desc": "Here you can configure protection against DNS rebinding attacks",
+ "rebinding_protection_enabled": "Enable protection from DNS rebinding attacks",
+ "rebinding_protection_enabled_desc": "If enabled, AdGuard Home will block responses containing host on the local network.",
+ "rebinding_allowed_hosts_title": "Allowed domains",
+ "rebinding_allowed_hosts_desc": "A list of domains. If configured, AdGuard Home will allow responses containing host on the local network from these domains."
}
diff --git a/client/src/actions/dnsConfig.js b/client/src/actions/dnsConfig.js
index c599ca8d..f7809de6 100644
--- a/client/src/actions/dnsConfig.js
+++ b/client/src/actions/dnsConfig.js
@@ -37,6 +37,9 @@ export const setDnsConfig = (config) => async (dispatch) => {
data.upstream_dns = splitByNewLine(config.upstream_dns);
hasDnsSettings = true;
}
+ if (Object.prototype.hasOwnProperty.call(data, 'rebinding_allowed_hosts')) {
+ data.rebinding_allowed_hosts = splitByNewLine(config.rebinding_allowed_hosts);
+ }
await apiClient.setDnsConfig(data);
diff --git a/client/src/components/Settings/Dns/Rebinding/Form.js b/client/src/components/Settings/Dns/Rebinding/Form.js
new file mode 100644
index 00000000..69b59abf
--- /dev/null
+++ b/client/src/components/Settings/Dns/Rebinding/Form.js
@@ -0,0 +1,91 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Field, reduxForm } from 'redux-form';
+import { Trans, useTranslation } from 'react-i18next';
+import { shallowEqual, useSelector } from 'react-redux';
+import { renderTextareaField, CheckboxField } from '../../../../helpers/form';
+import { removeEmptyLines } from '../../../../helpers/helpers';
+import { FORM_NAME } from '../../../../helpers/constants';
+
+const fields = [
+ {
+ id: 'rebinding_allowed_hosts',
+ title: 'rebinding_allowed_hosts_title',
+ subtitle: 'rebinding_allowed_hosts_desc',
+ normalizeOnBlur: removeEmptyLines,
+ },
+];
+
+const Form = ({
+ handleSubmit, submitting, invalid,
+}) => {
+ const { t } = useTranslation();
+ const { processingSetConfig } = useSelector((state) => state.dnsConfig, shallowEqual);
+
+ const renderField = ({
+ id, title, subtitle, disabled = processingSetConfig, normalizeOnBlur,
+ }) =>
+
+
+ {subtitle}
+
+
+
;
+
+ renderField.propTypes = {
+ id: PropTypes.string,
+ title: PropTypes.string,
+ subtitle: PropTypes.string,
+ disabled: PropTypes.bool,
+ normalizeOnBlur: PropTypes.func,
+ };
+
+ return (
+
+ );
+};
+
+Form.propTypes = {
+ handleSubmit: PropTypes.func.isRequired,
+ submitting: PropTypes.bool.isRequired,
+ invalid: PropTypes.bool.isRequired,
+};
+
+export default reduxForm({ form: FORM_NAME.REBINDING })(Form);
diff --git a/client/src/components/Settings/Dns/Rebinding/index.js b/client/src/components/Settings/Dns/Rebinding/index.js
new file mode 100644
index 00000000..c1e349fc
--- /dev/null
+++ b/client/src/components/Settings/Dns/Rebinding/index.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { shallowEqual, useDispatch, useSelector } from 'react-redux';
+import Form from './Form';
+import Card from '../../../ui/Card';
+import { setDnsConfig } from '../../../../actions/dnsConfig';
+
+const RebindingConfig = () => {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+ const {
+ rebinding_protection_enabled, rebinding_allowed_hosts,
+ } = useSelector((state) => state.dnsConfig, shallowEqual);
+
+ const handleFormSubmit = (values) => {
+ dispatch(setDnsConfig(values));
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default RebindingConfig;
diff --git a/client/src/components/Settings/Dns/index.js b/client/src/components/Settings/Dns/index.js
index 06c68f21..226585d3 100644
--- a/client/src/components/Settings/Dns/index.js
+++ b/client/src/components/Settings/Dns/index.js
@@ -8,6 +8,7 @@ import Config from './Config';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
import CacheConfig from './Cache';
+import RebindingConfig from './Rebinding';
import { getDnsConfig } from '../../../actions/dnsConfig';
import { getAccessList } from '../../../actions/access';
@@ -33,6 +34,7 @@ const Dns = () => {
+
>}
>;
};
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index 7bce8387..f8d567ce 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -509,6 +509,7 @@ export const FORM_NAME = {
INSTALL: 'install',
LOGIN: 'login',
CACHE: 'cache',
+ REBINDING: 'rebinding',
...DHCP_FORM_NAMES,
};
diff --git a/client/src/reducers/dnsConfig.js b/client/src/reducers/dnsConfig.js
index bbe4ad2f..b05d9991 100644
--- a/client/src/reducers/dnsConfig.js
+++ b/client/src/reducers/dnsConfig.js
@@ -16,6 +16,7 @@ const dnsConfig = handleActions(
blocking_ipv6,
upstream_dns,
bootstrap_dns,
+ rebinding_allowed_hosts,
...values
} = payload;
@@ -26,6 +27,7 @@ const dnsConfig = handleActions(
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
+ rebinding_allowed_hosts: (rebinding_allowed_hosts && rebinding_allowed_hosts.join('\n')) || '',
processingGetConfig: false,
};
},