Merge: + DNS Rewrites: support wildcard domain name

Closes #922

* commit '5ba45b91c9cfde84fc0866e6b3fca679504975e7':
  * client: fix render field for DNS settings
  + client: handle wildcard domains
  + DNS Rewrites: support wildcard domain name
This commit is contained in:
Ildar Kamalov 2019-12-23 14:02:21 +03:00
commit c5537968b1
7 changed files with 100 additions and 11 deletions

View File

@ -916,6 +916,8 @@ Response:
... ...
] ]
`domain` can be an exact host name (`www.host.com`) or a wildcard (`*.host.com`).
### API: Add a rewrite entry ### API: Add a rewrite entry

View File

@ -302,7 +302,7 @@
"setup_guide": "Setup guide", "setup_guide": "Setup guide",
"dns_addresses": "DNS addresses", "dns_addresses": "DNS addresses",
"dns_start": "DNS server is starting up", "dns_start": "DNS server is starting up",
"dns_status_error": "Error of getting DNS server status", "dns_status_error": "Error checking the DNS server status",
"down": "Down", "down": "Down",
"fix": "Fix", "fix": "Fix",
"dns_providers": "Here is a <0>list of known DNS providers</0> to choose from.", "dns_providers": "Here is a <0>list of known DNS providers</0> to choose from.",
@ -372,7 +372,7 @@
"rewrite_desc": "Allows to easily configure custom DNS response for a specific domain name.", "rewrite_desc": "Allows to easily configure custom DNS response for a specific domain name.",
"rewrite_applied": "Applied Rewrite rule", "rewrite_applied": "Applied Rewrite rule",
"dns_rewrites": "DNS rewrites", "dns_rewrites": "DNS rewrites",
"form_domain": "Enter domain", "form_domain": "Enter domain name or wildcard",
"form_answer": "Enter IP address or domain name", "form_answer": "Enter IP address or domain name",
"form_error_domain_format": "Invalid domain format", "form_error_domain_format": "Invalid domain format",
"form_error_answer_format": "Invalid answer format", "form_error_answer_format": "Invalid answer format",
@ -428,5 +428,8 @@
"whois": "Whois", "whois": "Whois",
"filtering_rules_learn_more": "<0>Learn more</0> about creating your own hosts blocklists.", "filtering_rules_learn_more": "<0>Learn more</0> about creating your own hosts blocklists.",
"blocked_by_response": "Blocked by CNAME or IP in response", "blocked_by_response": "Blocked by CNAME or IP in response",
"try_again": "Try again" "try_again": "Try again",
} "domain_desc": "Enter the domain name or wildcard you want to be rewritten.",
"example_rewrite_domain": "rewrite responses for this domain name only.",
"example_rewrite_wildcard": "rewrite responses for all <0>example.org</0> subdomains."
}

View File

@ -6,7 +6,7 @@ import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow'; import flow from 'lodash/flow';
import { import {
renderField, renderInputField,
renderRadioField, renderRadioField,
renderSelectField, renderSelectField,
required, required,
@ -45,7 +45,7 @@ let Form = ({
<Field <Field
name="ratelimit" name="ratelimit"
type="number" type="number"
component={renderField} component={renderInputField}
className="form-control" className="form-control"
placeholder={t('form_enter_rate_limit')} placeholder={t('form_enter_rate_limit')}
normalize={toNumber} normalize={toNumber}
@ -90,7 +90,7 @@ let Form = ({
</div> </div>
<Field <Field
name="blocking_ipv4" name="blocking_ipv4"
component={renderField} component={renderInputField}
className="form-control" className="form-control"
placeholder={t('form_enter_ip')} placeholder={t('form_enter_ip')}
validate={[ipv4, required]} validate={[ipv4, required]}
@ -107,7 +107,7 @@ let Form = ({
</div> </div>
<Field <Field
name="blocking_ipv6" name="blocking_ipv6"
component={renderField} component={renderInputField}
className="form-control" className="form-control"
placeholder={t('form_enter_ip')} placeholder={t('form_enter_ip')}
validate={[ipv6, required]} validate={[ipv6, required]}

View File

@ -20,6 +20,9 @@ const Form = (props) => {
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="modal-body"> <div className="modal-body">
<div className="form__desc form__desc--top">
<Trans>domain_desc</Trans>
</div>
<div className="form__group"> <div className="form__group">
<Field <Field
id="domain" id="domain"
@ -31,6 +34,22 @@ const Form = (props) => {
validate={[required, domain]} validate={[required, domain]}
/> />
</div> </div>
<Trans>examples_title</Trans>:
<ol className="leading-loose">
<li>
<code>example.org</code> <Trans>example_rewrite_domain</Trans>
</li>
<li>
<code>*.example.org</code> &nbsp;
<span>
<Trans components={[<code key="0">text</code>]}>
example_rewrite_wildcard
</Trans>
</span>
</li>
</ol>
<div className="form__group"> <div className="form__group">
<Field <Field
id="answer" id="answer"

View File

@ -1,5 +1,5 @@
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/[^/\s]+(\/.*)?$/; export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/[^/\s]+(\/.*)?$/;
export const R_HOST = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$/; export const R_HOST = /^(\*\.)?([\w-]+\.)+[\w-]+$/;
export const R_IPV4 = /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/; export const R_IPV4 = /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/;
export const R_IPV6 = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; export const R_IPV6 = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
export const R_CIDR = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/; export const R_CIDR = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/;

View File

@ -334,6 +334,13 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
return Result{}, nil return Result{}, nil
} }
// Return TRUE of host name matches a wildcard pattern
func matchDomainWildcard(host, wildcard string) bool {
return len(wildcard) >= 2 &&
wildcard[0] == '*' && wildcard[1] == '.' &&
strings.HasSuffix(host, wildcard[1:])
}
// Process rewrites table // Process rewrites table
// . Find CNAME for a domain name // . Find CNAME for a domain name
// . if found, set domain name to canonical name // . if found, set domain name to canonical name
@ -347,7 +354,9 @@ func (d *Dnsfilter) processRewrites(host string, qtype uint16) Result {
for _, r := range d.Rewrites { for _, r := range d.Rewrites {
if r.Domain != host { if r.Domain != host {
continue if !matchDomainWildcard(host, r.Domain) {
continue
}
} }
ip := net.ParseIP(r.Answer) ip := net.ParseIP(r.Answer)
@ -362,7 +371,9 @@ func (d *Dnsfilter) processRewrites(host string, qtype uint16) Result {
for _, r := range d.Rewrites { for _, r := range d.Rewrites {
if r.Domain != host { if r.Domain != host {
continue if !matchDomainWildcard(host, r.Domain) {
continue
}
} }
ip := net.ParseIP(r.Answer) ip := net.ParseIP(r.Answer)

View File

@ -474,6 +474,60 @@ func TestClientSettings(t *testing.T) {
assert.True(t, r.IsFiltered && r.Reason == FilteredBlockedService) assert.True(t, r.IsFiltered && r.Reason == FilteredBlockedService)
} }
func TestRewrites(t *testing.T) {
d := Dnsfilter{}
// CNAME, A, AAAA
d.Rewrites = []RewriteEntry{
RewriteEntry{"somecname", "somehost.com"},
RewriteEntry{"somehost.com", "0.0.0.0"},
RewriteEntry{"host.com", "1.2.3.4"},
RewriteEntry{"host.com", "1.2.3.5"},
RewriteEntry{"host.com", "1:2:3::4"},
RewriteEntry{"www.host.com", "host.com"},
}
r := d.processRewrites("host2.com", dns.TypeA)
assert.Equal(t, NotFilteredNotFound, r.Reason)
r = d.processRewrites("www.host.com", dns.TypeA)
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "host.com", r.CanonName)
assert.True(t, len(r.IPList) == 2)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
assert.True(t, r.IPList[1].Equal(net.ParseIP("1.2.3.5")))
r = d.processRewrites("www.host.com", dns.TypeAAAA)
assert.Equal(t, ReasonRewrite, r.Reason)
assert.True(t, len(r.IPList) == 1)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1:2:3::4")))
// wildcard
d.Rewrites = []RewriteEntry{
RewriteEntry{"*.host.com", "1.2.3.5"},
RewriteEntry{"host.com", "1.2.3.4"},
}
r = d.processRewrites("host.com", dns.TypeA)
assert.Equal(t, ReasonRewrite, r.Reason)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
r = d.processRewrites("www.host.com", dns.TypeA)
assert.Equal(t, ReasonRewrite, r.Reason)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.5")))
r = d.processRewrites("www.host2.com", dns.TypeA)
assert.Equal(t, NotFilteredNotFound, r.Reason)
// wildcard + CNAME
d.Rewrites = []RewriteEntry{
RewriteEntry{"*.host.com", "host.com"},
RewriteEntry{"host.com", "1.2.3.4"},
}
r = d.processRewrites("www.host.com", dns.TypeA)
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "host.com", r.CanonName)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
}
// BENCHMARKS // BENCHMARKS
func BenchmarkSafeBrowsing(b *testing.B) { func BenchmarkSafeBrowsing(b *testing.B) {