Pull request 2065: 6369-ratelimit-settings-ui

Closes #6369.
Co-authored-by: IldarKamalov <ik@adguard.com>

Squashed commit of the following:

commit efc824667a88765d5a16984fd17ecda2559f2b1e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Nov 15 19:10:47 2023 +0300

    all: imp docs

commit 9ec59b59000f005006ea231071329a586d9889ac
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Nov 15 17:21:03 2023 +0300

    dnsforward: imp err msg

commit d9710dfc1dcf74d5ee8386b053d7180316f21bce
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Nov 15 15:33:59 2023 +0300

    all: upd chlog

commit 29e868b93b15cfce5faed4d0c07b16decbce52f9
Merge: 1c3aec9f1 ebb06a583
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Nov 15 15:26:32 2023 +0300

    Merge branch 'master' into 6369-ratelimit-settings-ui

commit 1c3aec9f1478f71afa4d0aa9ba1c454e9d98b8db
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Nov 14 21:21:22 2023 +0300

    dnsforward: imp docs

commit 486bf86e5a2b51b6014a231386337a2d1e945c23
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Nov 13 09:57:21 2023 +0300

    fix linter

commit aec088f233737fdfa0e7086148ceb79df0d2e39a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Sun Nov 12 16:13:46 2023 +0300

    client: validate rate limit subnets

commit d4ca4d3a604295cdfaae54e6e461981233eabf3e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Nov 10 20:08:44 2023 +0300

    dnsforward: imp code

commit 5c11a1ef5c6fcc786d8496b14b9b16d1de1708cd
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Nov 10 15:07:56 2023 +0300

    all: ratelimit settings
This commit is contained in:
Stanislav Chzhen 2023-11-15 19:27:13 +03:00
parent ebb06a5831
commit 94bceaa84d
14 changed files with 500 additions and 43 deletions

View File

@ -23,12 +23,17 @@ See also the [v0.107.42 GitHub milestone][ms-v0.107.42].
NOTE: Add new changes BELOW THIS COMMENT.
-->
### Added
- Ability to specify rate limiting settings in the Web UI ([#6369]).
### Fixed
- Pre-filling the New static lease window with data ([#6402]).
- Protection pause timer synchronization ([#5759]).
[#5759]: https://github.com/AdguardTeam/AdGuardHome/issues/5759
[#6369]: https://github.com/AdguardTeam/AdGuardHome/issues/6369
[#6402]: https://github.com/AdguardTeam/AdGuardHome/issues/6402
<!--

View File

@ -310,6 +310,16 @@
"edns_use_custom_ip": "Use custom IP for EDNS",
"edns_use_custom_ip_desc": "Allow to use custom IP for EDNS",
"rate_limit_desc": "The number of requests per second allowed per client. Setting it to 0 means no limit.",
"rate_limit_subnet_len_ipv4": "Subnet prefix length for IPv4 addresses",
"rate_limit_subnet_len_ipv4_desc": "Subnet prefix length for IPv4 addresses used for rate limiting. The default is 24",
"rate_limit_subnet_len_ipv4_error": "The IPv4 subnet prefix length should be between 0 and 32",
"rate_limit_subnet_len_ipv6": "Subnet prefix length for IPv6 addresses",
"rate_limit_subnet_len_ipv6_desc": "Subnet prefix length for IPv6 addresses used for rate limiting. The default is 56",
"rate_limit_subnet_len_ipv6_error": "The IPv6 subnet prefix length should be between 0 and 128",
"form_enter_rate_limit_subnet_len": "Enter subnet prefix length for rate limiting",
"rate_limit_whitelist": "Rate limiting allowlist",
"rate_limit_whitelist_desc": "IP addresses excluded from rate limiting",
"rate_limit_whitelist_placeholder": "Enter one IP address per line",
"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_default": "Default: Respond with zero IP address (0.0.0.0 for A; :: for AAAA) when blocked by Adblock-style rule; respond with the IP address specified in the rule when blocked by /etc/hosts-style rule",

View File

@ -62,6 +62,10 @@ export const setDnsConfig = (config) => async (dispatch) => {
data.upstream_dns = splitByNewLine(config.upstream_dns);
hasDnsSettings = true;
}
if (Object.prototype.hasOwnProperty.call(data, 'ratelimit_whitelist')) {
data.ratelimit_whitelist = splitByNewLine(config.ratelimit_whitelist);
hasDnsSettings = true;
}
await apiClient.setDnsConfig(data);

View File

@ -6,6 +6,7 @@ import { Trans, useTranslation } from 'react-i18next';
import {
renderInputField,
renderRadioField,
renderTextareaField,
CheckboxField,
toNumber,
} from '../../../../helpers/form';
@ -14,7 +15,10 @@ import {
validateIpv6,
validateRequiredValue,
validateIp,
validateIPv4Subnet,
validateIPv6Subnet,
} from '../../../../helpers/validators';
import { removeEmptyLines } from '../../../../helpers/helpers';
import { BLOCKING_MODES, FORM_NAME, UINT32_RANGE } from '../../../../helpers/constants';
const checkboxes = [
@ -90,6 +94,69 @@ const Form = ({
/>
</div>
</div>
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<label htmlFor="ratelimit_subnet_len_ipv4"
className="form__label form__label--with-desc">
<Trans>rate_limit_subnet_len_ipv4</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_subnet_len_ipv4_desc</Trans>
</div>
<Field
name="ratelimit_subnet_len_ipv4"
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_rate_limit_subnet_len')}
normalize={toNumber}
validate={[validateRequiredValue, validateIPv4Subnet]}
min={0}
max={32}
/>
</div>
</div>
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<label htmlFor="ratelimit_subnet_len_ipv6"
className="form__label form__label--with-desc">
<Trans>rate_limit_subnet_len_ipv6</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_subnet_len_ipv6_desc</Trans>
</div>
<Field
name="ratelimit_subnet_len_ipv6"
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_rate_limit_subnet_len')}
normalize={toNumber}
validate={[validateRequiredValue, validateIPv6Subnet]}
min={0}
max={128}
/>
</div>
</div>
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<label htmlFor="ratelimit_whitelist"
className="form__label form__label--with-desc">
<Trans>rate_limit_whitelist</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_whitelist_desc</Trans>
</div>
<Field
name="ratelimit_whitelist"
component={renderTextareaField}
type="text"
className="form-control"
placeholder={t('rate_limit_whitelist_placeholder')}
normalizeOnBlur={removeEmptyLines}
/>
</div>
</div>
<div className="col-12">
<div className="form__group form__group--settings">
<Field

View File

@ -11,6 +11,9 @@ const Config = () => {
const {
blocking_mode,
ratelimit,
ratelimit_subnet_len_ipv4,
ratelimit_subnet_len_ipv6,
ratelimit_whitelist,
blocking_ipv4,
blocking_ipv6,
blocked_response_ttl,
@ -36,6 +39,9 @@ const Config = () => {
<Form
initialValues={{
ratelimit,
ratelimit_subnet_len_ipv4,
ratelimit_subnet_len_ipv6,
ratelimit_whitelist,
blocking_mode,
blocking_ipv4,
blocking_ipv6,

View File

@ -26,6 +26,10 @@ export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)
export const R_CLIENT_ID = /^[a-z0-9-]{1,63}$/;
export const R_IPV4_SUBNET = /^([0-9]|[1-2][0-9]|3[0-2])?$/;
export const R_IPV6_SUBNET = /^([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])?$/;
export const MIN_PASSWORD_LENGTH = 8;
export const MAX_PASSWORD_LENGTH = 72;

View File

@ -15,6 +15,8 @@ import {
R_DOMAIN,
MAX_PASSWORD_LENGTH,
MIN_PASSWORD_LENGTH,
R_IPV4_SUBNET,
R_IPV6_SUBNET,
} from './constants';
import { ip4ToInt, isValidAbsolutePath } from './form';
import { isIpInCidr, parseSubnetMask } from './helpers';
@ -365,3 +367,25 @@ export const validateIpGateway = (value, allValues) => {
}
return undefined;
};
/**
* @param value {string}
* @returns {Function}
*/
export const validateIPv4Subnet = (value) => {
if (!R_IPV4_SUBNET.test(value)) {
return i18next.t('rate_limit_subnet_len_ipv4_error');
}
return undefined;
};
/**
* @param value {string}
* @returns {Function}
*/
export const validateIPv6Subnet = (value) => {
if (!R_IPV6_SUBNET.test(value)) {
return i18next.t('rate_limit_subnet_len_ipv6_error');
}
return undefined;
};

View File

@ -18,6 +18,7 @@ const dnsConfig = handleActions(
fallback_dns,
bootstrap_dns,
local_ptr_upstreams,
ratelimit_whitelist,
...values
} = payload;
@ -30,6 +31,7 @@ const dnsConfig = handleActions(
fallback_dns: (fallback_dns && fallback_dns.join('\n')) || '',
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
local_ptr_upstreams: (local_ptr_upstreams && local_ptr_upstreams.join('\n')) || '',
ratelimit_whitelist: (ratelimit_whitelist && ratelimit_whitelist.join('\n')) || '',
processingGetConfig: false,
};
},

View File

@ -45,8 +45,19 @@ type jsonDNSConfig struct {
// ProtectionEnabled defines if protection is enabled.
ProtectionEnabled *bool `json:"protection_enabled"`
// RateLimit is the number of requests per second allowed per client.
RateLimit *uint32 `json:"ratelimit"`
// Ratelimit is the number of requests per second allowed per client.
Ratelimit *uint32 `json:"ratelimit"`
// RatelimitSubnetLenIPv4 is a subnet length for IPv4 addresses used for
// rate limiting requests.
RatelimitSubnetLenIPv4 *int `json:"ratelimit_subnet_len_ipv4"`
// RatelimitSubnetLenIPv6 is a subnet length for IPv6 addresses used for
// rate limiting requests.
RatelimitSubnetLenIPv6 *int `json:"ratelimit_subnet_len_ipv6"`
// RatelimitWhitelist is a list of IP addresses excluded from rate limiting.
RatelimitWhitelist *[]string `json:"ratelimit_whitelist"`
// BlockingMode defines the way blocked responses are constructed.
BlockingMode *filtering.BlockingMode `json:"blocking_mode"`
@ -121,6 +132,9 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
blockingMode, blockingIPv4, blockingIPv6 := s.dnsFilter.BlockingMode()
blockedResponseTTL := s.dnsFilter.BlockedResponseTTL()
ratelimit := s.conf.Ratelimit
ratelimitSubnetLenIPv4 := s.conf.RatelimitSubnetLenIPv4
ratelimitSubnetLenIPv6 := s.conf.RatelimitSubnetLenIPv6
ratelimitWhitelist := stringutil.CloneSliceOrEmpty(s.conf.RatelimitWhitelist)
customIP := s.conf.EDNSClientSubnet.CustomIP
enableEDNSClientSubnet := s.conf.EDNSClientSubnet.Enabled
@ -157,7 +171,10 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
BlockingMode: &blockingMode,
BlockingIPv4: blockingIPv4,
BlockingIPv6: blockingIPv6,
RateLimit: &ratelimit,
Ratelimit: &ratelimit,
RatelimitSubnetLenIPv4: &ratelimitSubnetLenIPv4,
RatelimitSubnetLenIPv6: &ratelimitSubnetLenIPv6,
RatelimitWhitelist: &ratelimitWhitelist,
EDNSCSCustomIP: customIP,
EDNSCSEnabled: &enableEDNSClientSubnet,
EDNSCSUseCustom: &useCustom,
@ -201,6 +218,7 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
aghhttp.WriteJSONResponseOK(w, r, resp)
}
// checkBlockingMode returns an error if blocking mode is invalid.
func (req *jsonDNSConfig) checkBlockingMode() (err error) {
if req.BlockingMode == nil {
return nil
@ -209,12 +227,21 @@ func (req *jsonDNSConfig) checkBlockingMode() (err error) {
return validateBlockingMode(*req.BlockingMode, req.BlockingIPv4, req.BlockingIPv6)
}
func (req *jsonDNSConfig) checkUpstreamsMode() bool {
valid := []string{"", "fastest_addr", "parallel"}
// checkUpstreamsMode returns an error if the upstream mode is invalid.
func (req *jsonDNSConfig) checkUpstreamsMode() (err error) {
if req.UpstreamMode == nil {
return nil
}
return req.UpstreamMode == nil || stringutil.InSlice(valid, *req.UpstreamMode)
mode := *req.UpstreamMode
if ok := slices.Contains([]string{"", "fastest_addr", "parallel"}, mode); !ok {
return fmt.Errorf("upstream_mode: incorrect value %q", mode)
}
return nil
}
// checkBootstrap returns an error if any bootstrap address is invalid.
func (req *jsonDNSConfig) checkBootstrap() (err error) {
if req.Bootstraps == nil {
return nil
@ -229,6 +256,7 @@ func (req *jsonDNSConfig) checkBootstrap() (err error) {
}
if _, err = upstream.NewUpstreamResolver(b, nil); err != nil {
// Don't wrap the error because it's informative enough as is.
return err
}
}
@ -244,67 +272,157 @@ func (req *jsonDNSConfig) checkFallbacks() (err error) {
err = ValidateUpstreams(*req.Fallbacks)
if err != nil {
return fmt.Errorf("validating fallback servers: %w", err)
return fmt.Errorf("fallback servers: %w", err)
}
return nil
}
// validate returns an error if any field of req is invalid.
//
// TODO(s.chzhen): Parse, don't validate.
func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) {
defer func() { err = errors.Annotate(err, "validating dns config: %w") }()
err = req.validateUpstreamDNSServers(privateNets)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
err = req.checkRatelimitSubnetMaskLen()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
err = req.checkRatelimitWhitelist()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
err = req.checkBlockingMode()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
err = req.checkUpstreamsMode()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
err = req.checkCacheTTL()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
return nil
}
// validateUpstreamDNSServers returns an error if any field of req is invalid.
func (req *jsonDNSConfig) validateUpstreamDNSServers(privateNets netutil.SubnetSet) (err error) {
if req.Upstreams != nil {
err = ValidateUpstreams(*req.Upstreams)
if err != nil {
return fmt.Errorf("validating upstream servers: %w", err)
return fmt.Errorf("upstream servers: %w", err)
}
}
if req.LocalPTRUpstreams != nil {
err = ValidateUpstreamsPrivate(*req.LocalPTRUpstreams, privateNets)
if err != nil {
return fmt.Errorf("validating private upstream servers: %w", err)
return fmt.Errorf("private upstream servers: %w", err)
}
}
err = req.checkBootstrap()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
err = req.checkFallbacks()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
err = req.checkBlockingMode()
if err != nil {
return err
}
switch {
case !req.checkUpstreamsMode():
return errors.Error("upstream_mode: incorrect value")
case !req.checkCacheTTL():
return errors.Error("cache_ttl_min must be less or equal than cache_ttl_max")
default:
return nil
}
}
func (req *jsonDNSConfig) checkCacheTTL() bool {
// checkCacheTTL returns an error if the configuration of the cache TTL is
// invalid.
func (req *jsonDNSConfig) checkCacheTTL() (err error) {
if req.CacheMinTTL == nil && req.CacheMaxTTL == nil {
return true
return nil
}
var min, max uint32
var minTTL, maxTTL uint32
if req.CacheMinTTL != nil {
min = *req.CacheMinTTL
minTTL = *req.CacheMinTTL
}
if req.CacheMaxTTL != nil {
max = *req.CacheMaxTTL
maxTTL = *req.CacheMaxTTL
}
return min <= max
if minTTL <= maxTTL {
return nil
}
return errors.Error("cache_ttl_min must be less or equal than cache_ttl_max")
}
// checkRatelimitSubnetMaskLen returns an error if the length of the subnet mask
// for IPv4 or IPv6 addresses is invalid.
func (req *jsonDNSConfig) checkRatelimitSubnetMaskLen() (err error) {
err = checkInclusion(req.RatelimitSubnetLenIPv4, 0, netutil.IPv4BitLen)
if err != nil {
return fmt.Errorf("ratelimit_subnet_len_ipv4 is invalid: %w", err)
}
err = checkInclusion(req.RatelimitSubnetLenIPv6, 0, netutil.IPv6BitLen)
if err != nil {
return fmt.Errorf("ratelimit_subnet_len_ipv6 is invalid: %w", err)
}
return nil
}
// checkInclusion returns an error if a ptr is not nil and points to value,
// that not in the inclusive range between minN and maxN.
func checkInclusion(ptr *int, minN, maxN int) (err error) {
if ptr == nil {
return nil
}
n := *ptr
switch {
case n < minN:
return fmt.Errorf("value %d less than min %d", n, minN)
case n > maxN:
return fmt.Errorf("value %d greater than max %d", n, maxN)
}
return nil
}
// checkRatelimitWhitelist returns an error if any of IP addresses is invalid.
func (req *jsonDNSConfig) checkRatelimitWhitelist() (err error) {
if req.RatelimitWhitelist == nil {
return nil
}
for i, ipStr := range *req.RatelimitWhitelist {
if _, err = netip.ParseAddr(ipStr); err != nil {
return fmt.Errorf("ratelimit whitelist: at index %d: %w", i, err)
}
}
return nil
}
// handleSetConfig handles requests to the POST /control/dns_config endpoint.
@ -401,6 +519,9 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
setIfNotNil(&s.conf.CacheOptimistic, dc.CacheOptimistic),
setIfNotNil(&s.conf.AddrProcConf.UseRDNS, dc.ResolveClients),
setIfNotNil(&s.conf.UsePrivateRDNS, dc.UsePrivateRDNS),
setIfNotNil(&s.conf.RatelimitSubnetLenIPv4, dc.RatelimitSubnetLenIPv4),
setIfNotNil(&s.conf.RatelimitSubnetLenIPv6, dc.RatelimitSubnetLenIPv6),
setIfNotNil(&s.conf.RatelimitWhitelist, dc.RatelimitWhitelist),
} {
shouldRestart = shouldRestart || hasSet
if shouldRestart {
@ -408,8 +529,8 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
}
}
if dc.RateLimit != nil && s.conf.Ratelimit != *dc.RateLimit {
s.conf.Ratelimit = *dc.RateLimit
if dc.Ratelimit != nil && s.conf.Ratelimit != *dc.Ratelimit {
s.conf.Ratelimit = *dc.Ratelimit
shouldRestart = true
}

View File

@ -74,6 +74,8 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
Config: Config{
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
FallbackDNS: []string{"9.9.9.10"},
RatelimitSubnetLenIPv4: 24,
RatelimitSubnetLenIPv6: 56,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ConfigModified: func() {},
@ -151,6 +153,8 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
TCPListenAddrs: []*net.TCPAddr{},
Config: Config{
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
RatelimitSubnetLenIPv4: 24,
RatelimitSubnetLenIPv6: 56,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
ConfigModified: func() {},
@ -180,10 +184,18 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
wantSet: "",
}, {
name: "blocking_mode_bad",
wantSet: "blocking_ipv4 must be valid ipv4 on custom_ip blocking_mode",
wantSet: "validating dns config: " +
"blocking_ipv4 must be valid ipv4 on custom_ip blocking_mode",
}, {
name: "ratelimit",
wantSet: "",
}, {
name: "ratelimit_subnet_len",
wantSet: "",
}, {
name: "ratelimit_whitelist_not_ip",
wantSet: `validating dns config: ratelimit whitelist: at index 1: ParseAddr("not.ip"): ` +
`unexpected character (at "not.ip")`,
}, {
name: "edns_cs_enabled",
wantSet: "",
@ -207,23 +219,25 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
wantSet: "",
}, {
name: "upstream_dns_bad",
wantSet: `validating upstream servers: validating upstream "!!!": not an ip:port`,
wantSet: `validating dns config: ` +
`upstream servers: validating upstream "!!!": not an ip:port`,
}, {
name: "bootstraps_bad",
wantSet: `checking bootstrap a: invalid address: bootstrap a:53: ` +
wantSet: `validating dns config: checking bootstrap a: invalid address: bootstrap a:53: ` +
`ParseAddr("a"): unable to parse IP`,
}, {
name: "cache_bad_ttl",
wantSet: `cache_ttl_min must be less or equal than cache_ttl_max`,
wantSet: `validating dns config: cache_ttl_min must be less or equal than cache_ttl_max`,
}, {
name: "upstream_mode_bad",
wantSet: `upstream_mode: incorrect value`,
wantSet: `validating dns config: upstream_mode: incorrect value "somethingelse"`,
}, {
name: "local_ptr_upstreams_good",
wantSet: "",
}, {
name: "local_ptr_upstreams_bad",
wantSet: `validating private upstream servers: checking domain-specific upstreams: ` +
wantSet: `validating dns config: ` +
`private upstream servers: checking domain-specific upstreams: ` +
`bad arpa domain name "non.arpa.": not a reversed ip network`,
}, {
name: "local_ptr_upstreams_null",

View File

@ -17,6 +17,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -53,6 +56,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -89,6 +95,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",

View File

@ -22,6 +22,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -60,6 +63,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -99,6 +105,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "refused",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -138,6 +147,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -177,6 +189,98 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 6,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
"upstream_mode": "",
"cache_size": 0,
"cache_ttl_min": 0,
"cache_ttl_max": 0,
"cache_optimistic": false,
"resolve_clients": false,
"use_private_ptr_resolvers": false,
"local_ptr_upstreams": [],
"edns_cs_use_custom": false,
"edns_cs_custom_ip": ""
}
},
"ratelimit_subnet_len": {
"req": {
"ratelimit": 12,
"ratelimit_subnet_len_ipv4": 32,
"ratelimit_subnet_len_ipv6": 128
},
"want": {
"upstream_dns": [
"8.8.8.8:53",
"8.8.4.4:53"
],
"upstream_dns_file": "",
"bootstrap_dns": [
"9.9.9.10",
"149.112.112.10",
"2620:fe::10",
"2620:fe::fe:10"
],
"fallback_dns": [],
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 12,
"ratelimit_subnet_len_ipv4": 32,
"ratelimit_subnet_len_ipv6": 128,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
"upstream_mode": "",
"cache_size": 0,
"cache_ttl_min": 0,
"cache_ttl_max": 0,
"cache_optimistic": false,
"resolve_clients": false,
"use_private_ptr_resolvers": false,
"local_ptr_upstreams": [],
"edns_cs_use_custom": false,
"edns_cs_custom_ip": ""
}
},
"ratelimit_whitelist_not_ip": {
"req": {
"ratelimit_whitelist": [
"1.2.3.4",
"not.ip"
]
},
"want": {
"upstream_dns": [
"8.8.8.8:53",
"8.8.4.4:53"
],
"upstream_dns_file": "",
"bootstrap_dns": [
"9.9.9.10",
"149.112.112.10",
"2620:fe::10",
"2620:fe::fe:10"
],
"fallback_dns": [],
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -216,6 +320,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -257,6 +364,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -298,6 +408,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -337,6 +450,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -376,6 +492,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -415,6 +534,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -454,6 +576,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -495,6 +620,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -536,6 +664,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -576,6 +707,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -615,6 +749,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -656,6 +793,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -700,6 +840,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -739,6 +882,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -782,6 +928,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -821,6 +970,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
@ -863,6 +1015,9 @@
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"ratelimit_subnet_len_ipv4": 24,
"ratelimit_subnet_len_ipv6": 56,
"ratelimit_whitelist": [],
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",

View File

@ -4,6 +4,25 @@
## v0.108.0: API changes
## v0.107.42: API changes
### The new field `"ratelimit_subnet_len_ipv4"` in `DNSConfig` object
* The new field `"ratelimit_subnet_len_ipv4"` in `GET /control/dns_info` and
`POST /control/dns_config` is the length of the subnet mask for IPv4
addresses.
### The new field `"ratelimit_subnet_len_ipv6"` in `DNSConfig` object
* The new field `"ratelimit_subnet_len_ipv6"` in `GET /control/dns_info` and
`POST /control/dns_config` is the length of the subnet mask for IPv6
addresses.
### The new field `"ratelimit_whitelist"` in `DNSConfig` object
* The new field `"blocked_response_ttl"` in `GET /control/dns_info` and `POST
/control/dns_config` is the list of IP addresses excluded from rate limiting.
## v0.107.39: API changes
### New HTTP API 'POST /control/dhcp/update_static_lease'

View File

@ -1468,6 +1468,23 @@
'type': 'boolean'
'ratelimit':
'type': 'integer'
'ratelimit_subnet_subnet_len_ipv4':
'description': 'Length of the subnet mask for IPv4 addresses.'
'type': 'integer'
'default': 24
'minimum': 0
'maximum': 32
'ratelimit_subnet_subnet_len_ipv6':
'description': 'Length of the subnet mask for IPv6 addresses.'
'type': 'integer'
'default': 56
'minimum': 0
'maximum': 128
'ratelimit_whitelist':
'type': 'array'
'description': 'List of IP addresses excluded from rate limiting.'
'items':
'type': 'string'
'blocking_mode':
'type': 'string'
'enum':