Skip to content

Commit

Permalink
Merge: + DNS: new settings (ratelimit, blocking mode, edns_client_sub…
Browse files Browse the repository at this point in the history
…net)

Close #1091 Close #1154 Close #1022

* commit '97e77cab643d6784067ce97c0f03ec3e4612c2c9':
  + client: handle EDNS Client Subnet setting
  + dns: add "edns_client_subnet" setting
  + client: handle DNS config
  * DNS: remove /enable_protection and /disable_protection
  + openapi: /dns_info, /dns_config
  * /control/set_upstreams_config: allow empty upstream list
  + dns: support blocking_mode=custom_ip
  + DNS: Get/Set DNS general settings
  • Loading branch information
szolin committed Dec 10, 2019
2 parents 92141e0 + 97e77ca commit 2b14a04
Show file tree
Hide file tree
Showing 19 changed files with 611 additions and 49 deletions.
47 changes: 47 additions & 0 deletions AGHTechDoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
17 changes: 17 additions & 0 deletions client/src/__locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -187,6 +188,22 @@
"query_log_disabled": "The query log is disabled and can be configured in the <0>settings</0>",
"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 settings</0>.",
"source_label": "Source",
"found_in_known_domain_db": "Found in the known domains database.",
"category_label": "Category",
Expand Down
35 changes: 35 additions & 0 deletions client/src/actions/dnsConfig.js
Original file line number Diff line number Diff line change
@@ -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());
}
};
12 changes: 2 additions & 10 deletions client/src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
30 changes: 18 additions & 12 deletions client/src/api/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions client/src/components/Settings/Clients/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ let Form = (props) => {
</div>
</div>
<div label="upstream" title={props.t('upstream_dns')}>
<div className="form__desc mb-3">
<Trans components={[<a href="#dns" key="0">link</a>]}>
upstream_dns_client_desc
</Trans>
</div>
<Field
id="upstreams"
name="upstreams"
Expand Down
153 changes: 153 additions & 0 deletions client/src/components/Settings/Dns/Config/Form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
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,
renderSelectField,
required,
ipv4,
ipv6,
biggerOrEqualZero,
toNumber,
} from '../../../../helpers/form';
import { BLOCKING_MODES } from '../../../../helpers/constants';

const getFields = (processing, t) => Object.values(BLOCKING_MODES).map(mode => (
<Field
key={mode}
name="blocking_mode"
type="radio"
component={renderRadioField}
value={mode}
placeholder={t(mode)}
disabled={processing}
/>
));

let Form = ({
handleSubmit, submitting, invalid, processing, blockingMode, t,
}) => (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="ratelimit" className="form__label form__label--with-desc">
<Trans>rate_limit</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_desc</Trans>
</div>
<Field
name="ratelimit"
type="number"
component={renderField}
className="form-control"
placeholder={t('form_enter_rate_limit')}
normalize={toNumber}
validate={[required, biggerOrEqualZero]}
/>
</div>
</div>
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name="edns_cs_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('edns_enable')}
disabled={processing}
subtitle={t('edns_cs_desc')}
/>
</div>
</div>
<div className="col-12">
<div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc">
<Trans>blocking_mode</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans components={[<div key="0">text</div>]}>blocking_mode_desc</Trans>
</div>
<div className="custom-controls-stacked">
{getFields(processing, t)}
</div>
</div>
</div>
{blockingMode === BLOCKING_MODES.custom_ip && (
<Fragment>
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="blocking_ipv4" className="form__label form__label--with-desc">
<Trans>blocking_ipv4</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>blocking_ipv4_desc</Trans>
</div>
<Field
name="blocking_ipv4"
component={renderField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[ipv4, required]}
/>
</div>
</div>
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="ip_address" className="form__label form__label--with-desc">
<Trans>blocking_ipv6</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>blocking_ipv6_desc</Trans>
</div>
<Field
name="blocking_ipv6"
component={renderField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[ipv6, required]}
/>
</div>
</div>
</Fragment>
)}
</div>
<button
type="submit"
className="btn btn-success btn-standard btn-large"
disabled={submitting || invalid || processing}
>
<Trans>save_btn</Trans>
</button>
</form>
);

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);
Loading

0 comments on commit 2b14a04

Please sign in to comment.