Pull request #886: all: allow multiple rules in dns filter results
Merge in DNS/adguard-home from 2102-rules-result to master Updates #2102. Squashed commit of the following: commit 47b2aa94c56b37be492c3c01e8111054612d9722 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Thu Dec 17 13:12:27 2020 +0300 querylog: remove pre-v0.99.3 compatibility code commit 2af0ee43c2444a7d842fcff057f2ba02f300244b Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Thu Dec 17 13:00:27 2020 +0300 all: improve documentation commit 3add300a42f0aa67bb315a448e294636c85d0b3b Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Dec 16 18:30:01 2020 +0300 all: improve changelog commit e04ef701fc2de7f4453729e617641c47e0883679 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Dec 16 17:56:53 2020 +0300 all: improve code and documentation commit 4f04845ae275ae4291869e00c62e4ff81b01eaa3 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Dec 16 17:01:08 2020 +0300 all: document changes, improve api commit bc59b7656a402d0c65f13bd74a71d8dda6a8a65d Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Tue Dec 15 18:22:01 2020 +0300 all: allow multiple rules in dns filter results
This commit is contained in:
parent
2c56a68597
commit
2e8352d31c
|
@ -1834,15 +1834,21 @@ Response:
|
||||||
|
|
||||||
{
|
{
|
||||||
"reason":"FilteredBlackList",
|
"reason":"FilteredBlackList",
|
||||||
"filter_id":1,
|
"rules":{
|
||||||
"rule":"||doubleclick.net^",
|
"filter_list_id":42,
|
||||||
"service_name": "...", // set if reason=FilteredBlockedService
|
"text":"||doubleclick.net^",
|
||||||
|
},
|
||||||
// if reason=ReasonRewrite:
|
// If we have "reason":"FilteredBlockedService".
|
||||||
|
"service_name": "...",
|
||||||
|
// If we have "reason":"Rewrite".
|
||||||
"cname": "...",
|
"cname": "...",
|
||||||
"ip_addrs": ["1.2.3.4", ...],
|
"ip_addrs": ["1.2.3.4", ...]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
There are also deprecated properties `filter_id` and `rule` on the top level of
|
||||||
|
the response object. Their usaga should be replaced with
|
||||||
|
`rules[*].filter_list_id` and `rules[*].text` correspondingly. See the
|
||||||
|
_OpenAPI_ documentation and the `./openapi/CHANGELOG.md` file.
|
||||||
|
|
||||||
## Log-in page
|
## Log-in page
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,9 @@ and this project adheres to
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Detecting of network interface configurated to have static IP address via
|
- The host checking API and the query logs API can now return multiple matched
|
||||||
|
rules ([#2102]).
|
||||||
|
- Detecting of network interface configured to have static IP address via
|
||||||
`/etc/network/interfaces` ([#2302]).
|
`/etc/network/interfaces` ([#2302]).
|
||||||
- DNSCrypt protocol support ([#1361]).
|
- DNSCrypt protocol support ([#1361]).
|
||||||
- A 5 second wait period until a DHCP server's network interface gets an IP
|
- A 5 second wait period until a DHCP server's network interface gets an IP
|
||||||
|
@ -24,6 +26,7 @@ and this project adheres to
|
||||||
- HTTP API request body size limit ([#2305]).
|
- HTTP API request body size limit ([#2305]).
|
||||||
|
|
||||||
[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361
|
[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361
|
||||||
|
[#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102
|
||||||
[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302
|
[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302
|
||||||
[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304
|
[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304
|
||||||
[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305
|
[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305
|
||||||
|
@ -64,7 +67,9 @@ and this project adheres to
|
||||||
[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345
|
[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345
|
||||||
[#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355
|
[#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Support for pre-v0.99.3 format of query logs ([#2102]).
|
||||||
|
|
||||||
## [v0.104.3] - 2020-11-19
|
## [v0.104.3] - 2020-11-19
|
||||||
|
|
||||||
|
|
17
HACKING.md
17
HACKING.md
|
@ -78,6 +78,14 @@ The rules are mostly sorted in the alphabetical order.
|
||||||
* Prefer constants to variables where possible. Reduce global variables. Use
|
* Prefer constants to variables where possible. Reduce global variables. Use
|
||||||
[constant errors] instead of `errors.New`.
|
[constant errors] instead of `errors.New`.
|
||||||
|
|
||||||
|
* Unused arguments in anonymous functions must be called `_`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
v.onSuccess = func(_ int, msg string) {
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
* Use linters.
|
* Use linters.
|
||||||
|
|
||||||
* Use named returns to improve readability of function signatures.
|
* Use named returns to improve readability of function signatures.
|
||||||
|
@ -110,6 +118,15 @@ The rules are mostly sorted in the alphabetical order.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When the implemented interface is unexported:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Unwrap implements the hidden wrapper interface for *fooError.
|
||||||
|
func (err *fooError) Unwrap() (unwrapped error) {
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Formatting
|
### Formatting
|
||||||
|
|
||||||
* Add an empty line before `break`, `continue`, `fallthrough`, and `return`,
|
* Add an empty line before `break`, `continue`, `fallthrough`, and `return`,
|
||||||
|
|
|
@ -188,7 +188,7 @@ func BlockedSvcKnown(s string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyBlockedServices - set blocked services settings for this DNS request
|
// ApplyBlockedServices - set blocked services settings for this DNS request
|
||||||
func (d *Dnsfilter) ApplyBlockedServices(setts *RequestFilteringSettings, list []string, global bool) {
|
func (d *DNSFilter) ApplyBlockedServices(setts *RequestFilteringSettings, list []string, global bool) {
|
||||||
setts.ServicesRules = []ServiceEntry{}
|
setts.ServicesRules = []ServiceEntry{}
|
||||||
if global {
|
if global {
|
||||||
d.confLock.RLock()
|
d.confLock.RLock()
|
||||||
|
@ -210,7 +210,7 @@ func (d *Dnsfilter) ApplyBlockedServices(setts *RequestFilteringSettings, list [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
|
||||||
d.confLock.RLock()
|
d.confLock.RLock()
|
||||||
list := d.Config.BlockedServices
|
list := d.Config.BlockedServices
|
||||||
d.confLock.RUnlock()
|
d.confLock.RUnlock()
|
||||||
|
@ -223,7 +223,7 @@ func (d *Dnsfilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Req
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
|
||||||
list := []string{}
|
list := []string{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&list)
|
err := json.NewDecoder(r.Body).Decode(&list)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -241,7 +241,7 @@ func (d *Dnsfilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerBlockedServicesHandlers - register HTTP handlers
|
// registerBlockedServicesHandlers - register HTTP handlers
|
||||||
func (d *Dnsfilter) registerBlockedServicesHandlers() {
|
func (d *DNSFilter) registerBlockedServicesHandlers() {
|
||||||
d.Config.HTTPRegister("GET", "/control/blocked_services/list", d.handleBlockedServicesList)
|
d.Config.HTTPRegister("GET", "/control/blocked_services/list", d.handleBlockedServicesList)
|
||||||
d.Config.HTTPRegister("POST", "/control/blocked_services/set", d.handleBlockedServicesSet)
|
d.Config.HTTPRegister("POST", "/control/blocked_services/set", d.handleBlockedServicesSet)
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,8 +91,8 @@ type filtersInitializerParams struct {
|
||||||
blockFilters []Filter
|
blockFilters []Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dnsfilter holds added rules and performs hostname matches against the rules
|
// DNSFilter matches hostnames and DNS requests against filtering rules.
|
||||||
type Dnsfilter struct {
|
type DNSFilter struct {
|
||||||
rulesStorage *filterlist.RuleStorage
|
rulesStorage *filterlist.RuleStorage
|
||||||
filteringEngine *urlfilter.DNSEngine
|
filteringEngine *urlfilter.DNSEngine
|
||||||
rulesStorageWhite *filterlist.RuleStorage
|
rulesStorageWhite *filterlist.RuleStorage
|
||||||
|
@ -129,7 +129,7 @@ const (
|
||||||
NotFilteredNotFound Reason = iota
|
NotFilteredNotFound Reason = iota
|
||||||
// NotFilteredWhiteList - the host is explicitly whitelisted
|
// NotFilteredWhiteList - the host is explicitly whitelisted
|
||||||
NotFilteredWhiteList
|
NotFilteredWhiteList
|
||||||
// NotFilteredError is return where there was an error during
|
// NotFilteredError is returned when there was an error during
|
||||||
// checking. Reserved, currently unused.
|
// checking. Reserved, currently unused.
|
||||||
NotFilteredError
|
NotFilteredError
|
||||||
|
|
||||||
|
@ -148,27 +148,32 @@ const (
|
||||||
// FilteredBlockedService - the host is blocked by "blocked services" settings
|
// FilteredBlockedService - the host is blocked by "blocked services" settings
|
||||||
FilteredBlockedService
|
FilteredBlockedService
|
||||||
|
|
||||||
// ReasonRewrite - rewrite rule was applied
|
// ReasonRewrite is returned when there was a rewrite by
|
||||||
|
// a legacy DNS Rewrite rule.
|
||||||
ReasonRewrite
|
ReasonRewrite
|
||||||
|
|
||||||
// RewriteEtcHosts - rewrite by /etc/hosts rule
|
// RewriteAutoHosts is returned when there was a rewrite by
|
||||||
RewriteEtcHosts
|
// autohosts rules (/etc/hosts and so on).
|
||||||
|
RewriteAutoHosts
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(a.garipov): Resync with actual code names or replace completely
|
||||||
|
// in HTTP API v1.
|
||||||
var reasonNames = []string{
|
var reasonNames = []string{
|
||||||
"NotFilteredNotFound",
|
NotFilteredNotFound: "NotFilteredNotFound",
|
||||||
"NotFilteredWhiteList",
|
NotFilteredWhiteList: "NotFilteredWhiteList",
|
||||||
"NotFilteredError",
|
NotFilteredError: "NotFilteredError",
|
||||||
|
|
||||||
"FilteredBlackList",
|
FilteredBlackList: "FilteredBlackList",
|
||||||
"FilteredSafeBrowsing",
|
FilteredSafeBrowsing: "FilteredSafeBrowsing",
|
||||||
"FilteredParental",
|
FilteredParental: "FilteredParental",
|
||||||
"FilteredInvalid",
|
FilteredInvalid: "FilteredInvalid",
|
||||||
"FilteredSafeSearch",
|
FilteredSafeSearch: "FilteredSafeSearch",
|
||||||
"FilteredBlockedService",
|
FilteredBlockedService: "FilteredBlockedService",
|
||||||
|
|
||||||
"Rewrite",
|
ReasonRewrite: "Rewrite",
|
||||||
"RewriteEtcHosts",
|
|
||||||
|
RewriteAutoHosts: "RewriteEtcHosts",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Reason) String() string {
|
func (r Reason) String() string {
|
||||||
|
@ -189,7 +194,7 @@ func (r Reason) In(reasons ...Reason) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig - get configuration
|
// GetConfig - get configuration
|
||||||
func (d *Dnsfilter) GetConfig() RequestFilteringSettings {
|
func (d *DNSFilter) GetConfig() RequestFilteringSettings {
|
||||||
c := RequestFilteringSettings{}
|
c := RequestFilteringSettings{}
|
||||||
// d.confLock.RLock()
|
// d.confLock.RLock()
|
||||||
c.SafeSearchEnabled = d.Config.SafeSearchEnabled
|
c.SafeSearchEnabled = d.Config.SafeSearchEnabled
|
||||||
|
@ -200,7 +205,7 @@ func (d *Dnsfilter) GetConfig() RequestFilteringSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteDiskConfig - write configuration
|
// WriteDiskConfig - write configuration
|
||||||
func (d *Dnsfilter) WriteDiskConfig(c *Config) {
|
func (d *DNSFilter) WriteDiskConfig(c *Config) {
|
||||||
d.confLock.Lock()
|
d.confLock.Lock()
|
||||||
*c = d.Config
|
*c = d.Config
|
||||||
c.Rewrites = rewriteArrayDup(d.Config.Rewrites)
|
c.Rewrites = rewriteArrayDup(d.Config.Rewrites)
|
||||||
|
@ -211,7 +216,7 @@ func (d *Dnsfilter) WriteDiskConfig(c *Config) {
|
||||||
// SetFilters - set new filters (synchronously or asynchronously)
|
// SetFilters - set new filters (synchronously or asynchronously)
|
||||||
// When filters are set asynchronously, the old filters continue working until the new filters are ready.
|
// When filters are set asynchronously, the old filters continue working until the new filters are ready.
|
||||||
// In this case the caller must ensure that the old filter files are intact.
|
// In this case the caller must ensure that the old filter files are intact.
|
||||||
func (d *Dnsfilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
|
func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
|
||||||
if async {
|
if async {
|
||||||
params := filtersInitializerParams{
|
params := filtersInitializerParams{
|
||||||
allowFilters: allowFilters,
|
allowFilters: allowFilters,
|
||||||
|
@ -245,7 +250,7 @@ func (d *Dnsfilter) SetFilters(blockFilters, allowFilters []Filter, async bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts initializing new filters by signal from channel
|
// Starts initializing new filters by signal from channel
|
||||||
func (d *Dnsfilter) filtersInitializer() {
|
func (d *DNSFilter) filtersInitializer() {
|
||||||
for {
|
for {
|
||||||
params := <-d.filtersInitializerChan
|
params := <-d.filtersInitializerChan
|
||||||
err := d.initFiltering(params.allowFilters, params.blockFilters)
|
err := d.initFiltering(params.allowFilters, params.blockFilters)
|
||||||
|
@ -257,13 +262,13 @@ func (d *Dnsfilter) filtersInitializer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close - close the object
|
// Close - close the object
|
||||||
func (d *Dnsfilter) Close() {
|
func (d *DNSFilter) Close() {
|
||||||
d.engineLock.Lock()
|
d.engineLock.Lock()
|
||||||
defer d.engineLock.Unlock()
|
defer d.engineLock.Unlock()
|
||||||
d.reset()
|
d.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) reset() {
|
func (d *DNSFilter) reset() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if d.rulesStorage != nil {
|
if d.rulesStorage != nil {
|
||||||
|
@ -290,34 +295,60 @@ type dnsFilterContext struct {
|
||||||
|
|
||||||
var gctx dnsFilterContext // global dnsfilter context
|
var gctx dnsFilterContext // global dnsfilter context
|
||||||
|
|
||||||
// Result holds state of hostname check
|
// ResultRule contains information about applied rules.
|
||||||
type Result struct {
|
type ResultRule struct {
|
||||||
IsFiltered bool `json:",omitempty"` // True if the host name is filtered
|
// FilterListID is the ID of the rule's filter list.
|
||||||
Reason Reason `json:",omitempty"` // Reason for blocking / unblocking
|
FilterListID int64 `json:",omitempty"`
|
||||||
Rule string `json:",omitempty"` // Original rule text
|
// Text is the text of the rule.
|
||||||
IP net.IP `json:",omitempty"` // Not nil only in the case of a hosts file syntax
|
Text string `json:",omitempty"`
|
||||||
FilterID int64 `json:",omitempty"` // Filter ID the rule belongs to
|
// IP is the host IP. It is nil unless the rule uses the
|
||||||
|
// /etc/hosts syntax or the reason is FilteredSafeSearch.
|
||||||
// for ReasonRewrite:
|
IP net.IP `json:",omitempty"`
|
||||||
CanonName string `json:",omitempty"` // CNAME value
|
|
||||||
|
|
||||||
// for RewriteEtcHosts:
|
|
||||||
ReverseHosts []string `json:",omitempty"`
|
|
||||||
|
|
||||||
// for ReasonRewrite & RewriteEtcHosts:
|
|
||||||
IPList []net.IP `json:",omitempty"` // list of IP addresses
|
|
||||||
|
|
||||||
// for FilteredBlockedService:
|
|
||||||
ServiceName string `json:",omitempty"` // Name of the blocked service
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matched can be used to see if any match at all was found, no matter filtered or not
|
// Result contains the result of a request check.
|
||||||
|
//
|
||||||
|
// All fields transitively have omitempty tags so that the query log
|
||||||
|
// doesn't become too large.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Clarify relationships between fields. Perhaps
|
||||||
|
// replace with a sum type or an interface?
|
||||||
|
type Result struct {
|
||||||
|
// IsFiltered is true if the request is filtered.
|
||||||
|
IsFiltered bool `json:",omitempty"`
|
||||||
|
|
||||||
|
// Reason is the reason for blocking or unblocking the request.
|
||||||
|
Reason Reason `json:",omitempty"`
|
||||||
|
|
||||||
|
// Rules are applied rules. If Rules are not empty, each rule
|
||||||
|
// is not nil.
|
||||||
|
Rules []*ResultRule `json:",omitempty"`
|
||||||
|
|
||||||
|
// ReverseHosts is the reverse lookup rewrite result. It is
|
||||||
|
// empty unless Reason is set to RewriteAutoHosts.
|
||||||
|
ReverseHosts []string `json:",omitempty"`
|
||||||
|
|
||||||
|
// IPList is the lookup rewrite result. It is empty unless
|
||||||
|
// Reason is set to RewriteAutoHosts or ReasonRewrite.
|
||||||
|
IPList []net.IP `json:",omitempty"`
|
||||||
|
|
||||||
|
// CanonName is the CNAME value from the lookup rewrite result.
|
||||||
|
// It is empty unless Reason is set to ReasonRewrite.
|
||||||
|
CanonName string `json:",omitempty"`
|
||||||
|
|
||||||
|
// ServiceName is the name of the blocked service. It is empty
|
||||||
|
// unless Reason is set to FilteredBlockedService.
|
||||||
|
ServiceName string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matched returns true if any match at all was found regardless of
|
||||||
|
// whether it was filtered or not.
|
||||||
func (r Reason) Matched() bool {
|
func (r Reason) Matched() bool {
|
||||||
return r != NotFilteredNotFound
|
return r != NotFilteredNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckHostRules tries to match the host against filtering rules only
|
// CheckHostRules tries to match the host against filtering rules only.
|
||||||
func (d *Dnsfilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
func (d *DNSFilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
||||||
if !setts.FilteringEnabled {
|
if !setts.FilteringEnabled {
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
@ -325,9 +356,9 @@ func (d *Dnsfilter) CheckHostRules(host string, qtype uint16, setts *RequestFilt
|
||||||
return d.matchHost(host, qtype, *setts)
|
return d.matchHost(host, qtype, *setts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckHost tries to match the host against filtering rules,
|
// CheckHost tries to match the host against filtering rules, then
|
||||||
// then safebrowsing and parental if they are enabled
|
// safebrowsing and parental control rules, if they are enabled.
|
||||||
func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
||||||
// sometimes DNS clients will try to resolve ".", which is a request to get root servers
|
// sometimes DNS clients will try to resolve ".", which is a request to get root servers
|
||||||
if host == "" {
|
if host == "" {
|
||||||
return Result{Reason: NotFilteredNotFound}, nil
|
return Result{Reason: NotFilteredNotFound}, nil
|
||||||
|
@ -413,10 +444,10 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool) {
|
func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool) {
|
||||||
ips := d.Config.AutoHosts.Process(host, qtype)
|
ips := d.Config.AutoHosts.Process(host, qtype)
|
||||||
if ips != nil {
|
if ips != nil {
|
||||||
result.Reason = RewriteEtcHosts
|
result.Reason = RewriteAutoHosts
|
||||||
result.IPList = ips
|
result.IPList = ips
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -424,7 +455,7 @@ func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (m
|
||||||
|
|
||||||
revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype)
|
revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype)
|
||||||
if len(revHosts) != 0 {
|
if len(revHosts) != 0 {
|
||||||
result.Reason = RewriteEtcHosts
|
result.Reason = RewriteAutoHosts
|
||||||
|
|
||||||
// TODO(a.garipov): Optimize this with a buffer.
|
// TODO(a.garipov): Optimize this with a buffer.
|
||||||
result.ReverseHosts = make([]string, len(revHosts))
|
result.ReverseHosts = make([]string, len(revHosts))
|
||||||
|
@ -445,7 +476,7 @@ func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (m
|
||||||
// . repeat for the new domain name (Note: we return only the last CNAME)
|
// . repeat for the new domain name (Note: we return only the last CNAME)
|
||||||
// . Find A or AAAA record for a domain name (exact match or by wildcard)
|
// . Find A or AAAA record for a domain name (exact match or by wildcard)
|
||||||
// . if found, set IP addresses (IPv4 or IPv6 depending on qtype) in Result.IPList array
|
// . if found, set IP addresses (IPv4 or IPv6 depending on qtype) in Result.IPList array
|
||||||
func (d *Dnsfilter) processRewrites(host string, qtype uint16) Result {
|
func (d *DNSFilter) processRewrites(host string, qtype uint16) Result {
|
||||||
var res Result
|
var res Result
|
||||||
|
|
||||||
d.confLock.RLock()
|
d.confLock.RLock()
|
||||||
|
@ -504,9 +535,16 @@ func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result {
|
||||||
res.Reason = FilteredBlockedService
|
res.Reason = FilteredBlockedService
|
||||||
res.IsFiltered = true
|
res.IsFiltered = true
|
||||||
res.ServiceName = s.Name
|
res.ServiceName = s.Name
|
||||||
res.Rule = rule.Text()
|
|
||||||
log.Debug("Blocked Services: matched rule: %s host: %s service: %s",
|
ruleText := rule.Text()
|
||||||
res.Rule, host, s.Name)
|
res.Rules = []*ResultRule{{
|
||||||
|
FilterListID: int64(rule.GetFilterListID()),
|
||||||
|
Text: ruleText,
|
||||||
|
}}
|
||||||
|
|
||||||
|
log.Debug("blocked services: matched rule: %s host: %s service: %s",
|
||||||
|
ruleText, host, s.Name)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -573,7 +611,7 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize urlfilter objects.
|
// Initialize urlfilter objects.
|
||||||
func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||||
rulesStorage, filteringEngine, err := createFilteringEngine(blockFilters)
|
rulesStorage, filteringEngine, err := createFilteringEngine(blockFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -600,7 +638,7 @@ func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||||
|
|
||||||
// matchHost is a low-level way to check only if hostname is filtered by rules,
|
// matchHost is a low-level way to check only if hostname is filtered by rules,
|
||||||
// skipping expensive safebrowsing and parental lookups.
|
// skipping expensive safebrowsing and parental lookups.
|
||||||
func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (Result, error) {
|
func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (Result, error) {
|
||||||
d.engineLock.RLock()
|
d.engineLock.RLock()
|
||||||
// Keep in mind that this lock must be held no just when calling Match()
|
// Keep in mind that this lock must be held no just when calling Match()
|
||||||
// but also while using the rules returned by it.
|
// but also while using the rules returned by it.
|
||||||
|
@ -658,7 +696,8 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||||
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
||||||
host, rule.Text(), rule.GetFilterListID())
|
host, rule.Text(), rule.GetFilterListID())
|
||||||
res := makeResult(rule, FilteredBlackList)
|
res := makeResult(rule, FilteredBlackList)
|
||||||
res.IP = rule.IP.To4()
|
res.Rules[0].IP = rule.IP.To4()
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,7 +706,8 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||||
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
||||||
host, rule.Text(), rule.GetFilterListID())
|
host, rule.Text(), rule.GetFilterListID())
|
||||||
res := makeResult(rule, FilteredBlackList)
|
res := makeResult(rule, FilteredBlackList)
|
||||||
res.IP = rule.IP
|
res.Rules[0].IP = rule.IP
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,22 +723,28 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||||
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
||||||
host, rule.Text(), rule.GetFilterListID())
|
host, rule.Text(), rule.GetFilterListID())
|
||||||
res := makeResult(rule, FilteredBlackList)
|
res := makeResult(rule, FilteredBlackList)
|
||||||
res.IP = net.IP{}
|
res.Rules[0].IP = net.IP{}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct Result object
|
// makeResult returns a properly constructed Result.
|
||||||
func makeResult(rule rules.Rule, reason Reason) Result {
|
func makeResult(rule rules.Rule, reason Reason) Result {
|
||||||
res := Result{}
|
res := Result{
|
||||||
res.FilterID = int64(rule.GetFilterListID())
|
Reason: reason,
|
||||||
res.Rule = rule.Text()
|
Rules: []*ResultRule{{
|
||||||
res.Reason = reason
|
FilterListID: int64(rule.GetFilterListID()),
|
||||||
|
Text: rule.Text(),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
if reason == FilteredBlackList {
|
if reason == FilteredBlackList {
|
||||||
res.IsFiltered = true
|
res.IsFiltered = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -708,7 +754,7 @@ func InitModule() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates properly initialized DNS Filter that is ready to be used.
|
// New creates properly initialized DNS Filter that is ready to be used.
|
||||||
func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
func New(c *Config, blockFilters []Filter) *DNSFilter {
|
||||||
if c != nil {
|
if c != nil {
|
||||||
cacheConf := cache.Config{
|
cacheConf := cache.Config{
|
||||||
EnableLRU: true,
|
EnableLRU: true,
|
||||||
|
@ -730,7 +776,7 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d := new(Dnsfilter)
|
d := new(DNSFilter)
|
||||||
|
|
||||||
err := d.initSecurityServices()
|
err := d.initSecurityServices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -768,7 +814,7 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
||||||
// Start - start the module:
|
// Start - start the module:
|
||||||
// . start async filtering initializer goroutine
|
// . start async filtering initializer goroutine
|
||||||
// . register web handlers
|
// . register web handlers
|
||||||
func (d *Dnsfilter) Start() {
|
func (d *DNSFilter) Start() {
|
||||||
d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
|
d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
|
||||||
go d.filtersInitializer()
|
go d.filtersInitializer()
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ func purgeCaches() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewForTest(c *Config, filters []Filter) *Dnsfilter {
|
func NewForTest(c *Config, filters []Filter) *DNSFilter {
|
||||||
setts = RequestFilteringSettings{}
|
setts = RequestFilteringSettings{}
|
||||||
setts.FilteringEnabled = true
|
setts.FilteringEnabled = true
|
||||||
if c != nil {
|
if c != nil {
|
||||||
|
@ -58,38 +58,48 @@ func NewForTest(c *Config, filters []Filter) *Dnsfilter {
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkMatch(t *testing.T, hostname string) {
|
func (d *DNSFilter) checkMatch(t *testing.T, hostname string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error while matching host %s: %s", hostname, err)
|
t.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if !ret.IsFiltered {
|
if !res.IsFiltered {
|
||||||
t.Errorf("Expected hostname %s to match", hostname)
|
t.Errorf("Expected hostname %s to match", hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) {
|
func (d *DNSFilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ret, err := d.CheckHost(hostname, qtype, &setts)
|
|
||||||
|
res, err := d.CheckHost(hostname, qtype, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error while matching host %s: %s", hostname, err)
|
t.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if !ret.IsFiltered {
|
|
||||||
|
if !res.IsFiltered {
|
||||||
t.Errorf("Expected hostname %s to match", hostname)
|
t.Errorf("Expected hostname %s to match", hostname)
|
||||||
}
|
}
|
||||||
if ret.IP == nil || ret.IP.String() != ip {
|
|
||||||
t.Errorf("Expected ip %s to match, actual: %v", ip, ret.IP)
|
if len(res.Rules) == 0 {
|
||||||
|
t.Errorf("Expected result to have rules")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r := res.Rules[0]
|
||||||
|
if r.IP == nil || r.IP.String() != ip {
|
||||||
|
t.Errorf("Expected ip %s to match, actual: %v", ip, r.IP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkMatchEmpty(t *testing.T, hostname string) {
|
func (d *DNSFilter) checkMatchEmpty(t *testing.T, hostname string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error while matching host %s: %s", hostname, err)
|
t.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if ret.IsFiltered {
|
if res.IsFiltered {
|
||||||
t.Errorf("Expected hostname %s to not match", hostname)
|
t.Errorf("Expected hostname %s to not match", hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,26 +130,43 @@ func TestEtcHostsMatching(t *testing.T) {
|
||||||
d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA)
|
d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA)
|
||||||
|
|
||||||
// ...but empty IPv6
|
// ...but empty IPv6
|
||||||
ret, err := d.CheckHost("block.com", dns.TypeAAAA, &setts)
|
res, err := d.CheckHost("block.com", dns.TypeAAAA, &setts)
|
||||||
assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ret.Rule == "0.0.0.0 block.com")
|
assert.True(t, res.IsFiltered)
|
||||||
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.Equal(t, "0.0.0.0 block.com", res.Rules[0].Text)
|
||||||
|
assert.Len(t, res.Rules[0].IP, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// IPv6
|
// IPv6
|
||||||
d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA)
|
d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA)
|
||||||
|
|
||||||
// ...but empty IPv4
|
// ...but empty IPv4
|
||||||
ret, err = d.CheckHost("ipv6.com", dns.TypeA, &setts)
|
res, err = d.CheckHost("ipv6.com", dns.TypeA, &setts)
|
||||||
assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0)
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, res.IsFiltered)
|
||||||
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.Equal(t, "::1 ipv6.com", res.Rules[0].Text)
|
||||||
|
assert.Len(t, res.Rules[0].IP, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// 2 IPv4 (return only the first one)
|
// 2 IPv4 (return only the first one)
|
||||||
ret, err = d.CheckHost("host2", dns.TypeA, &setts)
|
res, err = d.CheckHost("host2", dns.TypeA, &setts)
|
||||||
assert.True(t, err == nil && ret.IsFiltered)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("0.0.0.1")))
|
assert.True(t, res.IsFiltered)
|
||||||
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
loopback4 := net.IP{0, 0, 0, 1}
|
||||||
|
assert.Equal(t, res.Rules[0].IP, loopback4)
|
||||||
|
}
|
||||||
|
|
||||||
// ...and 1 IPv6 address
|
// ...and 1 IPv6 address
|
||||||
ret, err = d.CheckHost("host2", dns.TypeAAAA, &setts)
|
res, err = d.CheckHost("host2", dns.TypeAAAA, &setts)
|
||||||
assert.True(t, err == nil && ret.IsFiltered)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("::1")))
|
assert.True(t, res.IsFiltered)
|
||||||
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
loopback6 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||||
|
assert.Equal(t, res.Rules[0].IP, loopback6)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFE BROWSING
|
// SAFE BROWSING
|
||||||
|
@ -206,13 +233,11 @@ func TestCheckHostSafeSearchYandex(t *testing.T) {
|
||||||
|
|
||||||
// Check host for each domain
|
// Check host for each domain
|
||||||
for _, host := range yandex {
|
for _, host := range yandex {
|
||||||
result, err := d.CheckHost(host, dns.TypeA, &setts)
|
res, err := d.CheckHost(host, dns.TypeA, &setts)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Errorf("SafeSearch doesn't work for yandex domain `%s` cause %s", host, err)
|
assert.True(t, res.IsFiltered)
|
||||||
}
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56")
|
||||||
if result.IP.String() != "213.180.193.56" {
|
|
||||||
t.Errorf("SafeSearch doesn't work for yandex domain `%s`", host)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,13 +251,11 @@ func TestCheckHostSafeSearchGoogle(t *testing.T) {
|
||||||
|
|
||||||
// Check host for each domain
|
// Check host for each domain
|
||||||
for _, host := range googleDomains {
|
for _, host := range googleDomains {
|
||||||
result, err := d.CheckHost(host, dns.TypeA, &setts)
|
res, err := d.CheckHost(host, dns.TypeA, &setts)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Errorf("SafeSearch doesn't work for %s cause %s", host, err)
|
assert.True(t, res.IsFiltered)
|
||||||
}
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.NotEqual(t, res.Rules[0].IP.String(), "0.0.0.0")
|
||||||
if result.IP == nil {
|
|
||||||
t.Errorf("SafeSearch doesn't work for %s", host)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,40 +265,30 @@ func TestSafeSearchCacheYandex(t *testing.T) {
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
domain := "yandex.ru"
|
domain := "yandex.ru"
|
||||||
|
|
||||||
var result Result
|
// Check host with disabled safesearch.
|
||||||
var err error
|
res, err := d.CheckHost(domain, dns.TypeA, &setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
// Check host with disabled safesearch
|
assert.False(t, res.IsFiltered)
|
||||||
result, err = d.CheckHost(domain, dns.TypeA, &setts)
|
assert.Len(t, res.Rules, 0)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Cannot check host due to %s", err)
|
|
||||||
}
|
|
||||||
if result.IP != nil {
|
|
||||||
t.Fatalf("SafeSearch is not enabled but there is an answer for `%s` !", domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
result, err = d.CheckHost(domain, dns.TypeA, &setts)
|
res, err = d.CheckHost(domain, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
|
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fir yandex we already know valid ip
|
// For yandex we already know valid ip.
|
||||||
if result.IP.String() != "213.180.193.56" {
|
if assert.Len(t, res.Rules, 1) {
|
||||||
t.Fatalf("Wrong IP for %s safesearch: %s", domain, result.IP.String())
|
assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cache
|
// Check cache.
|
||||||
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
|
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
|
||||||
|
assert.True(t, isFound)
|
||||||
if !isFound {
|
if assert.Len(t, cachedValue.Rules, 1) {
|
||||||
t.Fatalf("Safesearch cache doesn't work for %s!", domain)
|
assert.Equal(t, cachedValue.Rules[0].IP.String(), "213.180.193.56")
|
||||||
}
|
|
||||||
|
|
||||||
if cachedValue.IP.String() != "213.180.193.56" {
|
|
||||||
t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,13 +296,10 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||||
d := NewForTest(nil, nil)
|
d := NewForTest(nil, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
domain := "www.google.ru"
|
domain := "www.google.ru"
|
||||||
result, err := d.CheckHost(domain, dns.TypeA, &setts)
|
res, err := d.CheckHost(domain, dns.TypeA, &setts)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Fatalf("Cannot check host due to %s", err)
|
assert.False(t, res.IsFiltered)
|
||||||
}
|
assert.Len(t, res.Rules, 0)
|
||||||
if result.IP != nil {
|
|
||||||
t.Fatalf("SafeSearch is not enabled but there is an answer!")
|
|
||||||
}
|
|
||||||
|
|
||||||
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
@ -313,25 +323,17 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err = d.CheckHost(domain, dns.TypeA, &setts)
|
res, err = d.CheckHost(domain, dns.TypeA, &setts)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.True(t, res.Rules[0].IP.Equal(ip))
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.IP.String() != ip.String() {
|
// Check cache.
|
||||||
t.Fatalf("Wrong IP for %s safesearch: %s. Should be: %s",
|
|
||||||
domain, result.IP.String(), ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cache
|
|
||||||
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
|
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
|
||||||
|
assert.True(t, isFound)
|
||||||
if !isFound {
|
if assert.Len(t, cachedValue.Rules, 1) {
|
||||||
t.Fatalf("Safesearch cache doesn't work for %s!", domain)
|
assert.True(t, cachedValue.Rules[0].IP.Equal(ip))
|
||||||
}
|
|
||||||
|
|
||||||
if cachedValue.IP.String() != ip.String() {
|
|
||||||
t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,15 +435,15 @@ func TestMatching(t *testing.T) {
|
||||||
d := NewForTest(nil, filters)
|
d := NewForTest(nil, filters)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
ret, err := d.CheckHost(test.hostname, test.dnsType, &setts)
|
res, err := d.CheckHost(test.hostname, test.dnsType, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error while matching host %s: %s", test.hostname, err)
|
t.Errorf("Error while matching host %s: %s", test.hostname, err)
|
||||||
}
|
}
|
||||||
if ret.IsFiltered != test.isFiltered {
|
if res.IsFiltered != test.isFiltered {
|
||||||
t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, ret.IsFiltered, test.isFiltered)
|
t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, res.IsFiltered, test.isFiltered)
|
||||||
}
|
}
|
||||||
if ret.Reason != test.reason {
|
if res.Reason != test.reason {
|
||||||
t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, ret.Reason.String(), test.reason.String())
|
t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, res.Reason.String(), test.reason.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -466,16 +468,20 @@ func TestWhitelist(t *testing.T) {
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
// matched by white filter
|
// matched by white filter
|
||||||
ret, err := d.CheckHost("host1", dns.TypeA, &setts)
|
res, err := d.CheckHost("host1", dns.TypeA, &setts)
|
||||||
assert.True(t, err == nil)
|
assert.True(t, err == nil)
|
||||||
assert.True(t, !ret.IsFiltered && ret.Reason == NotFilteredWhiteList)
|
assert.True(t, !res.IsFiltered && res.Reason == NotFilteredWhiteList)
|
||||||
assert.True(t, ret.Rule == "||host1^")
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.True(t, res.Rules[0].Text == "||host1^")
|
||||||
|
}
|
||||||
|
|
||||||
// not matched by white filter, but matched by block filter
|
// not matched by white filter, but matched by block filter
|
||||||
ret, err = d.CheckHost("host2", dns.TypeA, &setts)
|
res, err = d.CheckHost("host2", dns.TypeA, &setts)
|
||||||
assert.True(t, err == nil)
|
assert.True(t, err == nil)
|
||||||
assert.True(t, ret.IsFiltered && ret.Reason == FilteredBlackList)
|
assert.True(t, res.IsFiltered && res.Reason == FilteredBlackList)
|
||||||
assert.True(t, ret.Rule == "||host2^")
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.True(t, res.Rules[0].Text == "||host2^")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLIENT SETTINGS
|
// CLIENT SETTINGS
|
||||||
|
@ -559,11 +565,11 @@ func BenchmarkSafeBrowsing(b *testing.B) {
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
hostname := "wmconvirus.narod.ru"
|
hostname := "wmconvirus.narod.ru"
|
||||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Errorf("Error while matching host %s: %s", hostname, err)
|
b.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if !ret.IsFiltered {
|
if !res.IsFiltered {
|
||||||
b.Errorf("Expected hostname %s to match", hostname)
|
b.Errorf("Expected hostname %s to match", hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -575,11 +581,11 @@ func BenchmarkSafeBrowsingParallel(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
hostname := "wmconvirus.narod.ru"
|
hostname := "wmconvirus.narod.ru"
|
||||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Errorf("Error while matching host %s: %s", hostname, err)
|
b.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if !ret.IsFiltered {
|
if !res.IsFiltered {
|
||||||
b.Errorf("Expected hostname %s to match", hostname)
|
b.Errorf("Expected hostname %s to match", hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ func (r *RewriteEntry) prepare() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) prepareRewrites() {
|
func (d *DNSFilter) prepareRewrites() {
|
||||||
for i := range d.Rewrites {
|
for i := range d.Rewrites {
|
||||||
d.Rewrites[i].prepare()
|
d.Rewrites[i].prepare()
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ type rewriteEntryJSON struct {
|
||||||
Answer string `json:"answer"`
|
Answer string `json:"answer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
||||||
arr := []*rewriteEntryJSON{}
|
arr := []*rewriteEntryJSON{}
|
||||||
|
|
||||||
d.confLock.Lock()
|
d.confLock.Lock()
|
||||||
|
@ -169,7 +169,7 @@ func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
jsent := rewriteEntryJSON{}
|
jsent := rewriteEntryJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -191,7 +191,7 @@ func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
jsent := rewriteEntryJSON{}
|
jsent := rewriteEntryJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -218,7 +218,7 @@ func (d *Dnsfilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request)
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) registerRewritesHandlers() {
|
func (d *DNSFilter) registerRewritesHandlers() {
|
||||||
d.Config.HTTPRegister("GET", "/control/rewrite/list", d.handleRewriteList)
|
d.Config.HTTPRegister("GET", "/control/rewrite/list", d.handleRewriteList)
|
||||||
d.Config.HTTPRegister("POST", "/control/rewrite/add", d.handleRewriteAdd)
|
d.Config.HTTPRegister("POST", "/control/rewrite/add", d.handleRewriteAdd)
|
||||||
d.Config.HTTPRegister("POST", "/control/rewrite/delete", d.handleRewriteDelete)
|
d.Config.HTTPRegister("POST", "/control/rewrite/delete", d.handleRewriteDelete)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRewrites(t *testing.T) {
|
func TestRewrites(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// CNAME, A, AAAA
|
// CNAME, A, AAAA
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
{"somecname", "somehost.com", 0, nil},
|
{"somecname", "somehost.com", 0, nil},
|
||||||
|
@ -104,7 +104,7 @@ func TestRewrites(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewritesLevels(t *testing.T) {
|
func TestRewritesLevels(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// exact host, wildcard L2, wildcard L3
|
// exact host, wildcard L2, wildcard L3
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
{"host.com", "1.1.1.1", 0, nil},
|
{"host.com", "1.1.1.1", 0, nil},
|
||||||
|
@ -133,7 +133,7 @@ func TestRewritesLevels(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewritesExceptionCNAME(t *testing.T) {
|
func TestRewritesExceptionCNAME(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// wildcard; exception for a sub-domain
|
// wildcard; exception for a sub-domain
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
{"*.host.com", "2.2.2.2", 0, nil},
|
{"*.host.com", "2.2.2.2", 0, nil},
|
||||||
|
@ -153,7 +153,7 @@ func TestRewritesExceptionCNAME(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewritesExceptionWC(t *testing.T) {
|
func TestRewritesExceptionWC(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// wildcard; exception for a sub-wildcard
|
// wildcard; exception for a sub-wildcard
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
{"*.host.com", "2.2.2.2", 0, nil},
|
{"*.host.com", "2.2.2.2", 0, nil},
|
||||||
|
@ -173,7 +173,7 @@ func TestRewritesExceptionWC(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewritesExceptionIP(t *testing.T) {
|
func TestRewritesExceptionIP(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// exception for AAAA record
|
// exception for AAAA record
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
{"host.com", "1.2.3.4", 0, nil},
|
{"host.com", "1.2.3.4", 0, nil},
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// Safe Browsing, Parental Control
|
|
||||||
|
|
||||||
package dnsfilter
|
package dnsfilter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -22,6 +20,8 @@ import (
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Safe browsing and parental control methods.
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dnsTimeout = 3 * time.Second
|
dnsTimeout = 3 * time.Second
|
||||||
defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
|
defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
|
||||||
|
@ -30,7 +30,7 @@ const (
|
||||||
pcTXTSuffix = `pc.dns.adguard.com.`
|
pcTXTSuffix = `pc.dns.adguard.com.`
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d *Dnsfilter) initSecurityServices() error {
|
func (d *DNSFilter) initSecurityServices() error {
|
||||||
var err error
|
var err error
|
||||||
d.safeBrowsingServer = defaultSafebrowsingServer
|
d.safeBrowsingServer = defaultSafebrowsingServer
|
||||||
d.parentalServer = defaultParentalServer
|
d.parentalServer = defaultParentalServer
|
||||||
|
@ -287,7 +287,7 @@ func check(c *sbCtx, r Result, u upstream.Upstream) (Result, error) {
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
|
func (d *DNSFilter) checkSafeBrowsing(host string) (Result, error) {
|
||||||
if log.GetLevel() >= log.DEBUG {
|
if log.GetLevel() >= log.DEBUG {
|
||||||
timer := log.StartTimer()
|
timer := log.StartTimer()
|
||||||
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
|
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
|
||||||
|
@ -301,12 +301,14 @@ func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
|
||||||
res := Result{
|
res := Result{
|
||||||
IsFiltered: true,
|
IsFiltered: true,
|
||||||
Reason: FilteredSafeBrowsing,
|
Reason: FilteredSafeBrowsing,
|
||||||
Rule: "adguard-malware-shavar",
|
Rules: []*ResultRule{{
|
||||||
|
Text: "adguard-malware-shavar",
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
return check(ctx, res, d.safeBrowsingUpstream)
|
return check(ctx, res, d.safeBrowsingUpstream)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
func (d *DNSFilter) checkParental(host string) (Result, error) {
|
||||||
if log.GetLevel() >= log.DEBUG {
|
if log.GetLevel() >= log.DEBUG {
|
||||||
timer := log.StartTimer()
|
timer := log.StartTimer()
|
||||||
defer timer.LogElapsed("Parental lookup for %s", host)
|
defer timer.LogElapsed("Parental lookup for %s", host)
|
||||||
|
@ -320,7 +322,9 @@ func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
||||||
res := Result{
|
res := Result{
|
||||||
IsFiltered: true,
|
IsFiltered: true,
|
||||||
Reason: FilteredParental,
|
Reason: FilteredParental,
|
||||||
Rule: "parental CATEGORY_BLACKLISTED",
|
Rules: []*ResultRule{{
|
||||||
|
Text: "parental CATEGORY_BLACKLISTED",
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
return check(ctx, res, d.parentalUpstream)
|
return check(ctx, res, d.parentalUpstream)
|
||||||
}
|
}
|
||||||
|
@ -331,17 +335,17 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string,
|
||||||
http.Error(w, text, code)
|
http.Error(w, text, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.SafeBrowsingEnabled = true
|
d.Config.SafeBrowsingEnabled = true
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.SafeBrowsingEnabled = false
|
d.Config.SafeBrowsingEnabled = false
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"enabled": d.Config.SafeBrowsingEnabled,
|
"enabled": d.Config.SafeBrowsingEnabled,
|
||||||
}
|
}
|
||||||
|
@ -358,17 +362,17 @@ func (d *Dnsfilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Requ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.ParentalEnabled = true
|
d.Config.ParentalEnabled = true
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.ParentalEnabled = false
|
d.Config.ParentalEnabled = false
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"enabled": d.Config.ParentalEnabled,
|
"enabled": d.Config.ParentalEnabled,
|
||||||
}
|
}
|
||||||
|
@ -386,7 +390,7 @@ func (d *Dnsfilter) handleParentalStatus(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) registerSecurityHandlers() {
|
func (d *DNSFilter) registerSecurityHandlers() {
|
||||||
d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
|
d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
|
||||||
d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
|
d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
|
||||||
d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus)
|
d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus)
|
|
@ -18,7 +18,7 @@ import (
|
||||||
expire byte[4]
|
expire byte[4]
|
||||||
res Result
|
res Result
|
||||||
*/
|
*/
|
||||||
func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int {
|
func (d *DNSFilter) setCacheResult(cache cache.Cache, host string, res Result) int {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
||||||
|
@ -63,12 +63,12 @@ func getCachedResult(cache cache.Cache, host string) (Result, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SafeSearchDomain returns replacement address for search engine
|
// SafeSearchDomain returns replacement address for search engine
|
||||||
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
|
func (d *DNSFilter) SafeSearchDomain(host string) (string, bool) {
|
||||||
val, ok := safeSearchDomains[host]
|
val, ok := safeSearchDomains[host]
|
||||||
return val, ok
|
return val, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
func (d *DNSFilter) checkSafeSearch(host string) (Result, error) {
|
||||||
if log.GetLevel() >= log.DEBUG {
|
if log.GetLevel() >= log.DEBUG {
|
||||||
timer := log.StartTimer()
|
timer := log.StartTimer()
|
||||||
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
||||||
|
@ -87,49 +87,52 @@ func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
|
res := Result{
|
||||||
|
IsFiltered: true,
|
||||||
|
Reason: FilteredSafeSearch,
|
||||||
|
Rules: []*ResultRule{{}},
|
||||||
|
}
|
||||||
|
|
||||||
if ip := net.ParseIP(safeHost); ip != nil {
|
if ip := net.ParseIP(safeHost); ip != nil {
|
||||||
res.IP = ip
|
res.Rules[0].IP = ip
|
||||||
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this address should be resolved with upstream that was configured in dnsforward
|
// TODO this address should be resolved with upstream that was configured in dnsforward
|
||||||
addrs, err := net.LookupIP(safeHost)
|
ips, err := net.LookupIP(safeHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
||||||
return Result{}, err
|
return Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range addrs {
|
for _, ip := range ips {
|
||||||
if ipv4 := i.To4(); ipv4 != nil {
|
if ipv4 := ip.To4(); ipv4 != nil {
|
||||||
res.IP = ipv4
|
res.Rules[0].IP = ipv4
|
||||||
break
|
|
||||||
|
l := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||||
|
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, l)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(res.IP) == 0 {
|
|
||||||
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache result
|
func (d *DNSFilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
|
||||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
|
|
||||||
d.Config.SafeSearchEnabled = true
|
d.Config.SafeSearchEnabled = true
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.SafeSearchEnabled = false
|
d.Config.SafeSearchEnabled = false
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"enabled": d.Config.SafeSearchEnabled,
|
"enabled": d.Config.SafeSearchEnabled,
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ var webRegistered bool
|
||||||
// The zero Server is empty and ready for use.
|
// The zero Server is empty and ready for use.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
dnsProxy *proxy.Proxy // DNS proxy instance
|
dnsProxy *proxy.Proxy // DNS proxy instance
|
||||||
dnsFilter *dnsfilter.Dnsfilter // DNS filter instance
|
dnsFilter *dnsfilter.DNSFilter // DNS filter instance
|
||||||
dhcpServer dhcpd.ServerInterface // DHCP server instance (optional)
|
dhcpServer dhcpd.ServerInterface // DHCP server instance (optional)
|
||||||
queryLog querylog.QueryLog // Query log instance
|
queryLog querylog.QueryLog // Query log instance
|
||||||
stats stats.Stats
|
stats stats.Stats
|
||||||
|
@ -74,7 +74,7 @@ type Server struct {
|
||||||
|
|
||||||
// DNSCreateParams - parameters for NewServer()
|
// DNSCreateParams - parameters for NewServer()
|
||||||
type DNSCreateParams struct {
|
type DNSCreateParams struct {
|
||||||
DNSFilter *dnsfilter.Dnsfilter
|
DNSFilter *dnsfilter.DNSFilter
|
||||||
Stats stats.Stats
|
Stats stats.Stats
|
||||||
QueryLog querylog.QueryLog
|
QueryLog querylog.QueryLog
|
||||||
DHCPServer dhcpd.ServerInterface
|
DHCPServer dhcpd.ServerInterface
|
||||||
|
|
|
@ -296,7 +296,7 @@ func TestBlockedRequest(t *testing.T) {
|
||||||
|
|
||||||
func TestServerCustomClientUpstream(t *testing.T) {
|
func TestServerCustomClientUpstream(t *testing.T) {
|
||||||
s := createTestServer(t)
|
s := createTestServer(t)
|
||||||
s.conf.GetCustomUpstreamByClient = func(clientAddr string) *proxy.UpstreamConfig {
|
s.conf.GetCustomUpstreamByClient = func(_ string) *proxy.UpstreamConfig {
|
||||||
uc := &proxy.UpstreamConfig{}
|
uc := &proxy.UpstreamConfig{}
|
||||||
u := &testUpstream{}
|
u := &testUpstream{}
|
||||||
u.ipv4 = map[string][]net.IP{}
|
u.ipv4 = map[string][]net.IP{}
|
||||||
|
@ -473,7 +473,7 @@ func TestBlockCNAME(t *testing.T) {
|
||||||
func TestClientRulesForCNAMEMatching(t *testing.T) {
|
func TestClientRulesForCNAMEMatching(t *testing.T) {
|
||||||
s := createTestServer(t)
|
s := createTestServer(t)
|
||||||
testUpstm := &testUpstream{testCNAMEs, testIPv4, nil}
|
testUpstm := &testUpstream{testCNAMEs, testIPv4, nil}
|
||||||
s.conf.FilterHandler = func(clientAddr string, settings *dnsfilter.RequestFilteringSettings) {
|
s.conf.FilterHandler = func(_ string, settings *dnsfilter.RequestFilteringSettings) {
|
||||||
settings.FilteringEnabled = false
|
settings.FilteringEnabled = false
|
||||||
}
|
}
|
||||||
err := s.startWithUpstream(testUpstm)
|
err := s.startWithUpstream(testUpstm)
|
||||||
|
@ -863,6 +863,8 @@ func sendTestMessages(t *testing.T, conn *dns.Conn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host, ip string) {
|
func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host, ip string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
req := createTestMessage(host)
|
req := createTestMessage(host)
|
||||||
reply, _, err := client.Exchange(req, addr.String())
|
reply, _, err := client.Exchange(req, addr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -900,6 +902,8 @@ func assertGoogleAResponse(t *testing.T, reply *dns.Msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertResponse(t *testing.T, reply *dns.Msg, ip string) {
|
func assertResponse(t *testing.T, reply *dns.Msg, ip string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if len(reply.Answer) != 1 {
|
if len(reply.Answer) != 1 {
|
||||||
t.Fatalf("DNS server returned reply with wrong number of answers - %d", len(reply.Answer))
|
t.Fatalf("DNS server returned reply with wrong number of answers - %d", len(reply.Answer))
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,13 +52,13 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
// Return immediately if there's an error
|
// Return immediately if there's an error
|
||||||
return nil, fmt.Errorf("dnsfilter failed to check host %q: %w", host, err)
|
return nil, fmt.Errorf("dnsfilter failed to check host %q: %w", host, err)
|
||||||
} else if res.IsFiltered {
|
} else if res.IsFiltered {
|
||||||
log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule)
|
log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rules[0].Text)
|
||||||
d.Res = s.genDNSFilterMessage(d, &res)
|
d.Res = s.genDNSFilterMessage(d, &res)
|
||||||
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
|
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
|
||||||
ctx.origQuestion = d.Req.Question[0]
|
ctx.origQuestion = d.Req.Question[0]
|
||||||
// resolve canonical name, not the original host name
|
// resolve canonical name, not the original host name
|
||||||
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
|
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||||
} else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHosts) != 0 {
|
} else if res.Reason == dnsfilter.RewriteAutoHosts && len(res.ReverseHosts) != 0 {
|
||||||
resp := s.makeResponse(req)
|
resp := s.makeResponse(req)
|
||||||
for _, h := range res.ReverseHosts {
|
for _, h := range res.ReverseHosts {
|
||||||
hdr := dns.RR_Header{
|
hdr := dns.RR_Header{
|
||||||
|
@ -77,7 +77,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Res = resp
|
d.Res = resp
|
||||||
} else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts {
|
} else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteAutoHosts {
|
||||||
resp := s.makeResponse(req)
|
resp := s.makeResponse(req)
|
||||||
|
|
||||||
name := host
|
name := host
|
||||||
|
|
|
@ -39,8 +39,11 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
|
||||||
// If the query was filtered by "Safe search", dnsfilter also must return
|
// If the query was filtered by "Safe search", dnsfilter also must return
|
||||||
// the IP address that must be used in response.
|
// the IP address that must be used in response.
|
||||||
// In this case regardless of the filtering method, we should return it
|
// In this case regardless of the filtering method, we should return it
|
||||||
if result.Reason == dnsfilter.FilteredSafeSearch && result.IP != nil {
|
if result.Reason == dnsfilter.FilteredSafeSearch &&
|
||||||
return s.genResponseWithIP(m, result.IP)
|
len(result.Rules) > 0 &&
|
||||||
|
result.Rules[0].IP != nil {
|
||||||
|
|
||||||
|
return s.genResponseWithIP(m, result.Rules[0].IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.conf.BlockingMode == "null_ip" {
|
if s.conf.BlockingMode == "null_ip" {
|
||||||
|
@ -68,8 +71,8 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
|
||||||
// Default blocking mode
|
// Default blocking mode
|
||||||
// If there's an IP specified in the rule, return it
|
// If there's an IP specified in the rule, return it
|
||||||
// For host-type rules, return null IP
|
// For host-type rules, return null IP
|
||||||
if result.IP != nil {
|
if len(result.Rules) > 0 && result.Rules[0].IP != nil {
|
||||||
return s.genResponseWithIP(m, result.IP)
|
return s.genResponseWithIP(m, result.Rules[0].IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.makeResponseNullIP(m)
|
return s.makeResponseNullIP(m)
|
||||||
|
|
|
@ -346,11 +346,23 @@ func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request
|
||||||
enableFilters(true)
|
enableFilters(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type checkHostRespRule struct {
|
||||||
|
FilterListID int64 `json:"filter_list_id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
type checkHostResp struct {
|
type checkHostResp struct {
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
|
|
||||||
|
// FilterID is the ID of the rule's filter list.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Rules[*].FilterListID.
|
||||||
FilterID int64 `json:"filter_id"`
|
FilterID int64 `json:"filter_id"`
|
||||||
|
|
||||||
Rule string `json:"rule"`
|
Rule string `json:"rule"`
|
||||||
|
|
||||||
|
Rules []*checkHostRespRule `json:"rules"`
|
||||||
|
|
||||||
// for FilteredBlockedService:
|
// for FilteredBlockedService:
|
||||||
SvcName string `json:"service_name"`
|
SvcName string `json:"service_name"`
|
||||||
|
|
||||||
|
@ -374,11 +386,20 @@ func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
resp := checkHostResp{}
|
resp := checkHostResp{}
|
||||||
resp.Reason = result.Reason.String()
|
resp.Reason = result.Reason.String()
|
||||||
resp.FilterID = result.FilterID
|
resp.FilterID = result.Rules[0].FilterListID
|
||||||
resp.Rule = result.Rule
|
resp.Rule = result.Rules[0].Text
|
||||||
resp.SvcName = result.ServiceName
|
resp.SvcName = result.ServiceName
|
||||||
resp.CanonName = result.CanonName
|
resp.CanonName = result.CanonName
|
||||||
resp.IPList = result.IPList
|
resp.IPList = result.IPList
|
||||||
|
|
||||||
|
resp.Rules = make([]*checkHostRespRule, len(result.Rules))
|
||||||
|
for i, r := range result.Rules {
|
||||||
|
resp.Rules[i] = &checkHostRespRule{
|
||||||
|
FilterListID: r.FilterListID,
|
||||||
|
Text: r.Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
js, err := json.Marshal(resp)
|
js, err := json.Marshal(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
|
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
|
||||||
|
|
|
@ -58,7 +58,7 @@ type homeContext struct {
|
||||||
dnsServer *dnsforward.Server // DNS module
|
dnsServer *dnsforward.Server // DNS module
|
||||||
rdns *RDNS // rDNS module
|
rdns *RDNS // rDNS module
|
||||||
whois *Whois // WHOIS module
|
whois *Whois // WHOIS module
|
||||||
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
|
dnsFilter *dnsfilter.DNSFilter // DNS filtering module
|
||||||
dhcpServer *dhcpd.Server // DHCP module
|
dhcpServer *dhcpd.Server // DHCP module
|
||||||
auth *Auth // HTTP authentication module
|
auth *Auth // HTTP authentication module
|
||||||
filters Filtering // DNS filtering module
|
filters Filtering // DNS filtering module
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type logEntryHandler (func(t json.Token, ent *logEntry) error)
|
type logEntryHandler (func(t json.Token, ent *logEntry) error)
|
||||||
|
@ -85,6 +84,29 @@ var logEntryHandlers = map[string]logEntryHandler{
|
||||||
ent.OrigAnswer, err = base64.StdEncoding.DecodeString(v)
|
ent.OrigAnswer, err = base64.StdEncoding.DecodeString(v)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
|
"Upstream": func(t json.Token, ent *logEntry) error {
|
||||||
|
v, ok := t.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ent.Upstream = v
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
"Elapsed": func(t json.Token, ent *logEntry) error {
|
||||||
|
v, ok := t.(json.Number)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
i, err := v.Int64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ent.Elapsed = time.Duration(i)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultHandlers = map[string]logEntryHandler{
|
||||||
"IsFiltered": func(t json.Token, ent *logEntry) error {
|
"IsFiltered": func(t json.Token, ent *logEntry) error {
|
||||||
v, ok := t.(bool)
|
v, ok := t.(bool)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -94,23 +116,40 @@ var logEntryHandlers = map[string]logEntryHandler{
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
"Rule": func(t json.Token, ent *logEntry) error {
|
"Rule": func(t json.Token, ent *logEntry) error {
|
||||||
v, ok := t.(string)
|
s, ok := t.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ent.Result.Rule = v
|
|
||||||
|
l := len(ent.Result.Rules)
|
||||||
|
if l == 0 {
|
||||||
|
ent.Result.Rules = []*dnsfilter.ResultRule{{}}
|
||||||
|
l++
|
||||||
|
}
|
||||||
|
|
||||||
|
ent.Result.Rules[l-1].Text = s
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
"FilterID": func(t json.Token, ent *logEntry) error {
|
"FilterID": func(t json.Token, ent *logEntry) error {
|
||||||
v, ok := t.(json.Number)
|
n, ok := t.(json.Number)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
i, err := v.Int64()
|
|
||||||
|
i, err := n.Int64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ent.Result.FilterID = i
|
|
||||||
|
l := len(ent.Result.Rules)
|
||||||
|
if l == 0 {
|
||||||
|
ent.Result.Rules = []*dnsfilter.ResultRule{{}}
|
||||||
|
l++
|
||||||
|
}
|
||||||
|
|
||||||
|
ent.Result.Rules[l-1].FilterListID = i
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
"Reason": func(t json.Token, ent *logEntry) error {
|
"Reason": func(t json.Token, ent *logEntry) error {
|
||||||
|
@ -133,62 +172,50 @@ var logEntryHandlers = map[string]logEntryHandler{
|
||||||
ent.Result.ServiceName = v
|
ent.Result.ServiceName = v
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
"Upstream": func(t json.Token, ent *logEntry) error {
|
|
||||||
v, ok := t.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
ent.Upstream = v
|
|
||||||
return nil
|
func decodeResult(dec *json.Decoder, ent *logEntry) {
|
||||||
},
|
for {
|
||||||
"Elapsed": func(t json.Token, ent *logEntry) error {
|
keyToken, err := dec.Token()
|
||||||
v, ok := t.(json.Number)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
i, err := v.Int64()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if err != io.EOF {
|
||||||
|
log.Debug("decodeResult err: %s", err)
|
||||||
}
|
}
|
||||||
ent.Elapsed = time.Duration(i)
|
|
||||||
return nil
|
return
|
||||||
},
|
}
|
||||||
"Result": func(json.Token, *logEntry) error {
|
|
||||||
return nil
|
if d, ok := keyToken.(json.Delim); ok {
|
||||||
},
|
if d == '}' {
|
||||||
"Question": func(t json.Token, ent *logEntry) error {
|
return
|
||||||
v, ok := t.(string)
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key, ok := keyToken.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
log.Debug("decodeResult: keyToken is %T (%[1]v) and not string", keyToken)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
var qstr []byte
|
|
||||||
qstr, err := base64.StdEncoding.DecodeString(v)
|
handler, ok := resultHandlers[key]
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q := new(dns.Msg)
|
|
||||||
err = q.Unpack(qstr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ent.QHost = q.Question[0].Name
|
|
||||||
if len(ent.QHost) == 0 {
|
|
||||||
return nil // nil???
|
|
||||||
}
|
|
||||||
ent.QHost = ent.QHost[:len(ent.QHost)-1]
|
|
||||||
ent.QType = dns.TypeToString[q.Question[0].Qtype]
|
|
||||||
ent.QClass = dns.ClassToString[q.Question[0].Qclass]
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
"Time": func(t json.Token, ent *logEntry) error {
|
|
||||||
v, ok := t.(string)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := dec.Token()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = handler(val, ent); err != nil {
|
||||||
|
log.Debug("decodeResult handler err: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
ent.Time, err = time.Parse(time.RFC3339, v)
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeLogEntry(ent *logEntry, str string) {
|
func decodeLogEntry(ent *logEntry, str string) {
|
||||||
|
@ -200,18 +227,27 @@ func decodeLogEntry(ent *logEntry, str string) {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
log.Debug("decodeLogEntry err: %s", err)
|
log.Debug("decodeLogEntry err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := keyToken.(json.Delim); ok {
|
if _, ok := keyToken.(json.Delim); ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
key, ok := keyToken.(string)
|
key, ok := keyToken.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Debug("decodeLogEntry: keyToken is %T and not string", keyToken)
|
log.Debug("decodeLogEntry: keyToken is %T (%[1]v) and not string", keyToken)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key == "Result" {
|
||||||
|
decodeResult(dec, ent)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
handler, ok := logEntryHandlers[key]
|
handler, ok := logEntryHandlers[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
|
@ -223,7 +259,8 @@ func decodeLogEntry(ent *logEntry, str string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = handler(val, ent); err != nil {
|
if err = handler(val, ent); err != nil {
|
||||||
log.Debug("decodeLogEntry err: %s", err)
|
log.Debug("decodeLogEntry handler err: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,29 +21,17 @@ func TestDecode_decodeQueryLog(t *testing.T) {
|
||||||
log string
|
log string
|
||||||
want string
|
want string
|
||||||
}{{
|
}{{
|
||||||
name: "back_compatibility_all_right",
|
name: "all_right",
|
||||||
log: `{"Question":"ULgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
|
|
||||||
want: "default",
|
|
||||||
}, {
|
|
||||||
name: "back_compatibility_bad_msg",
|
|
||||||
log: `{"Question":"","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
|
|
||||||
want: "decodeLogEntry err: dns: overflow unpacking uint16\n",
|
|
||||||
}, {
|
|
||||||
name: "back_compatibility_bad_decoding",
|
|
||||||
log: `{"Question":"LgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
|
|
||||||
want: "decodeLogEntry err: illegal base64 data at input byte 48\n",
|
|
||||||
}, {
|
|
||||||
name: "modern_all_right",
|
|
||||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||||
want: "default",
|
want: "default",
|
||||||
}, {
|
}, {
|
||||||
name: "bad_filter_id",
|
name: "bad_filter_id",
|
||||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1.5},"Elapsed":837429}`,
|
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1.5},"Elapsed":837429}`,
|
||||||
want: "decodeLogEntry err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n",
|
want: "decodeResult handler err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n",
|
||||||
}, {
|
}, {
|
||||||
name: "bad_is_filtered",
|
name: "bad_is_filtered",
|
||||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":trooe,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":trooe,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||||
want: "default",
|
want: "decodeLogEntry err: invalid character 'o' in literal true (expecting 'u')\n",
|
||||||
}, {
|
}, {
|
||||||
name: "bad_elapsed",
|
name: "bad_elapsed",
|
||||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":-1}`,
|
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":-1}`,
|
||||||
|
@ -55,7 +43,7 @@ func TestDecode_decodeQueryLog(t *testing.T) {
|
||||||
}, {
|
}, {
|
||||||
name: "bad_time",
|
name: "bad_time",
|
||||||
log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||||
want: "decodeLogEntry err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n",
|
want: "decodeLogEntry handler err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n",
|
||||||
}, {
|
}, {
|
||||||
name: "bad_host",
|
name: "bad_host",
|
||||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||||
|
@ -75,7 +63,7 @@ func TestDecode_decodeQueryLog(t *testing.T) {
|
||||||
}, {
|
}, {
|
||||||
name: "very_bad_client_proto",
|
name: "very_bad_client_proto",
|
||||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"dog","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"dog","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||||
want: "decodeLogEntry err: invalid client proto: \"dog\"\n",
|
want: "decodeLogEntry handler err: invalid client proto: \"dog\"\n",
|
||||||
}, {
|
}, {
|
||||||
name: "bad_answer",
|
name: "bad_answer",
|
||||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":0.9,"Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":0.9,"Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||||
|
@ -83,7 +71,7 @@ func TestDecode_decodeQueryLog(t *testing.T) {
|
||||||
}, {
|
}, {
|
||||||
name: "very_bad_answer",
|
name: "very_bad_answer",
|
||||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||||
want: "decodeLogEntry err: illegal base64 data at input byte 61\n",
|
want: "decodeLogEntry handler err: illegal base64 data at input byte 61\n",
|
||||||
}, {
|
}, {
|
||||||
name: "bad_rule",
|
name: "bad_rule",
|
||||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":false,"FilterID":1},"Elapsed":837429}`,
|
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":false,"FilterID":1},"Elapsed":837429}`,
|
||||||
|
@ -102,7 +90,7 @@ func TestDecode_decodeQueryLog(t *testing.T) {
|
||||||
l := &logEntry{}
|
l := &logEntry{}
|
||||||
decodeLogEntry(l, tc.log)
|
decodeLogEntry(l, tc.log)
|
||||||
|
|
||||||
assert.True(t, strings.HasSuffix(logOutput.String(), tc.want), logOutput.String())
|
assert.True(t, strings.HasSuffix(logOutput.String(), tc.want), "%q\ndoes not end with\n%q", logOutput.String(), tc.want)
|
||||||
|
|
||||||
logOutput.Reset()
|
logOutput.Reset()
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,10 +6,13 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(a.garipov): Use a proper structured approach here.
|
||||||
|
|
||||||
// Get Client IP address
|
// Get Client IP address
|
||||||
func (l *queryLog) getClientIP(clientIP string) string {
|
func (l *queryLog) getClientIP(clientIP string) string {
|
||||||
if l.conf.AnonymizeClientIP {
|
if l.conf.AnonymizeClientIP {
|
||||||
|
@ -29,10 +32,12 @@ func (l *queryLog) getClientIP(clientIP string) string {
|
||||||
return clientIP
|
return clientIP
|
||||||
}
|
}
|
||||||
|
|
||||||
// entriesToJSON - converts log entries to JSON
|
// jobject is a JSON object alias.
|
||||||
func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[string]interface{} {
|
type jobject = map[string]interface{}
|
||||||
// init the response object
|
|
||||||
data := []map[string]interface{}{}
|
// entriesToJSON converts query log entries to JSON.
|
||||||
|
func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) (res jobject) {
|
||||||
|
data := []jobject{}
|
||||||
|
|
||||||
// the elements order is already reversed (from newer to older)
|
// the elements order is already reversed (from newer to older)
|
||||||
for i := 0; i < len(entries); i++ {
|
for i := 0; i < len(entries); i++ {
|
||||||
|
@ -41,17 +46,18 @@ func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[stri
|
||||||
data = append(data, jsonEntry)
|
data = append(data, jsonEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := map[string]interface{}{}
|
res = jobject{
|
||||||
result["oldest"] = ""
|
"data": data,
|
||||||
|
"oldest": "",
|
||||||
|
}
|
||||||
if !oldest.IsZero() {
|
if !oldest.IsZero() {
|
||||||
result["oldest"] = oldest.Format(time.RFC3339Nano)
|
res["oldest"] = oldest.Format(time.RFC3339Nano)
|
||||||
}
|
|
||||||
result["data"] = data
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *queryLog) logEntryToJSONEntry(entry *logEntry) (jsonEntry jobject) {
|
||||||
var msg *dns.Msg
|
var msg *dns.Msg
|
||||||
|
|
||||||
if len(entry.Answer) > 0 {
|
if len(entry.Answer) > 0 {
|
||||||
|
@ -62,17 +68,18 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonEntry := map[string]interface{}{
|
jsonEntry = jobject{
|
||||||
"reason": entry.Result.Reason.String(),
|
"reason": entry.Result.Reason.String(),
|
||||||
"elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64),
|
"elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64),
|
||||||
"time": entry.Time.Format(time.RFC3339Nano),
|
"time": entry.Time.Format(time.RFC3339Nano),
|
||||||
"client": l.getClientIP(entry.IP),
|
"client": l.getClientIP(entry.IP),
|
||||||
"client_proto": entry.ClientProto,
|
"client_proto": entry.ClientProto,
|
||||||
}
|
"upstream": entry.Upstream,
|
||||||
jsonEntry["question"] = map[string]interface{}{
|
"question": jobject{
|
||||||
"host": entry.QHost,
|
"host": entry.QHost,
|
||||||
"type": entry.QType,
|
"type": entry.QType,
|
||||||
"class": entry.QClass,
|
"class": entry.QClass,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
|
@ -83,12 +90,15 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
|
||||||
if opt != nil {
|
if opt != nil {
|
||||||
dnssecOk = opt.Do()
|
dnssecOk = opt.Do()
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonEntry["answer_dnssec"] = dnssecOk
|
jsonEntry["answer_dnssec"] = dnssecOk
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entry.Result.Rule) > 0 {
|
jsonEntry["rules"] = resultRulesToJSONRules(entry.Result.Rules)
|
||||||
jsonEntry["rule"] = entry.Result.Rule
|
|
||||||
jsonEntry["filterId"] = entry.Result.FilterID
|
if len(entry.Result.Rules) > 0 && len(entry.Result.Rules[0].Text) > 0 {
|
||||||
|
jsonEntry["rule"] = entry.Result.Rules[0].Text
|
||||||
|
jsonEntry["filterId"] = entry.Result.Rules[0].FilterListID
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entry.Result.ServiceName) != 0 {
|
if len(entry.Result.ServiceName) != 0 {
|
||||||
|
@ -113,20 +123,30 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonEntry["upstream"] = entry.Upstream
|
|
||||||
|
|
||||||
return jsonEntry
|
return jsonEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func answerToMap(a *dns.Msg) []map[string]interface{} {
|
func resultRulesToJSONRules(rules []*dnsfilter.ResultRule) (jsonRules []jobject) {
|
||||||
|
jsonRules = make([]jobject, len(rules))
|
||||||
|
for i, r := range rules {
|
||||||
|
jsonRules[i] = jobject{
|
||||||
|
"filter_list_id": r.FilterListID,
|
||||||
|
"text": r.Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonRules
|
||||||
|
}
|
||||||
|
|
||||||
|
func answerToMap(a *dns.Msg) (answers []jobject) {
|
||||||
if a == nil || len(a.Answer) == 0 {
|
if a == nil || len(a.Answer) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
answers := []map[string]interface{}{}
|
answers = []jobject{}
|
||||||
for _, k := range a.Answer {
|
for _, k := range a.Answer {
|
||||||
header := k.Header()
|
header := k.Header()
|
||||||
answer := map[string]interface{}{
|
answer := jobject{
|
||||||
"type": dns.TypeToString[header.Rrtype],
|
"type": dns.TypeToString[header.Rrtype],
|
||||||
"ttl": header.Ttl,
|
"ttl": header.Ttl,
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,10 +236,12 @@ func addEntry(l *queryLog, host, answerStr, client string) {
|
||||||
a.Answer = append(a.Answer, answer)
|
a.Answer = append(a.Answer, answer)
|
||||||
res := dnsfilter.Result{
|
res := dnsfilter.Result{
|
||||||
IsFiltered: true,
|
IsFiltered: true,
|
||||||
Rule: "SomeRule",
|
|
||||||
Reason: dnsfilter.ReasonRewrite,
|
Reason: dnsfilter.ReasonRewrite,
|
||||||
ServiceName: "SomeService",
|
ServiceName: "SomeService",
|
||||||
FilterID: 1,
|
Rules: []*dnsfilter.ResultRule{{
|
||||||
|
FilterListID: 1,
|
||||||
|
Text: "SomeRule",
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
params := AddParams{
|
params := AddParams{
|
||||||
Question: &q,
|
Question: &q,
|
||||||
|
|
|
@ -117,7 +117,7 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool {
|
||||||
res.Reason.In(
|
res.Reason.In(
|
||||||
dnsfilter.NotFilteredWhiteList,
|
dnsfilter.NotFilteredWhiteList,
|
||||||
dnsfilter.ReasonRewrite,
|
dnsfilter.ReasonRewrite,
|
||||||
dnsfilter.RewriteEtcHosts,
|
dnsfilter.RewriteAutoHosts,
|
||||||
)
|
)
|
||||||
|
|
||||||
case filteringStatusBlocked:
|
case filteringStatusBlocked:
|
||||||
|
@ -137,7 +137,7 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool {
|
||||||
return res.Reason == dnsfilter.NotFilteredWhiteList
|
return res.Reason == dnsfilter.NotFilteredWhiteList
|
||||||
|
|
||||||
case filteringStatusRewritten:
|
case filteringStatusRewritten:
|
||||||
return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteEtcHosts)
|
return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteAutoHosts)
|
||||||
|
|
||||||
case filteringStatusSafeSearch:
|
case filteringStatusSafeSearch:
|
||||||
return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeSearch
|
return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeSearch
|
||||||
|
|
|
@ -18,7 +18,7 @@ require (
|
||||||
golang.org/x/mod v0.4.0 // indirect
|
golang.org/x/mod v0.4.0 // indirect
|
||||||
golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7
|
golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
honnef.co/go/tools v0.0.1-2020.1.6
|
honnef.co/go/tools v0.1.0
|
||||||
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475
|
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475
|
||||||
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7
|
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7
|
||||||
)
|
)
|
||||||
|
|
|
@ -123,6 +123,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
|
@ -158,6 +159,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc=
|
honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
|
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
|
||||||
|
honnef.co/go/tools v0.1.0 h1:AWNL1W1i7f0wNZ8VwOKNJ0sliKvOF/adn0EHenfUh+c=
|
||||||
|
honnef.co/go/tools v0.1.0/go.mod h1:XtegFAyX/PfluP4921rXU5IkjkqBCDnUq4W8VCIoKvM=
|
||||||
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 h1:5ZmJGYyuTlhdlIpRxSFhdJqkXQweXETFCEaLhRAX3e8=
|
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 h1:5ZmJGYyuTlhdlIpRxSFhdJqkXQweXETFCEaLhRAX3e8=
|
||||||
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0=
|
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0=
|
||||||
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY=
|
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY=
|
||||||
|
|
|
@ -1,5 +1,37 @@
|
||||||
# AdGuard Home API Change Log
|
# AdGuard Home API Change Log
|
||||||
|
|
||||||
|
<!-- TODO(a.garipov): Reformat in accordance with the KeepAChangelog spec. -->
|
||||||
|
|
||||||
|
## v0.105: API changes
|
||||||
|
|
||||||
|
### Multiple matched rules in `GET /filtering/check_host` and `GET /querylog`
|
||||||
|
|
||||||
|
<!-- TODO(a.garipov): Update with better examples once $dnsrewrite rules are
|
||||||
|
checked in. -->
|
||||||
|
|
||||||
|
* The properties `rule` and `filter_id` are now deprecated. API users should
|
||||||
|
inspect the newly-added `rules` object array instead. Currently, it's either
|
||||||
|
empty or contains one object, which contains the same things as the old two
|
||||||
|
properties did, but under more correct names:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
// …
|
||||||
|
|
||||||
|
// Deprecated.
|
||||||
|
"rule": "||example.com^",
|
||||||
|
// Deprecated.
|
||||||
|
"filter_id": 42,
|
||||||
|
// Newly-added.
|
||||||
|
"rules": [{
|
||||||
|
"text": "||example.com^",
|
||||||
|
"filter_list_id": 42
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The old fields will be removed in v0.106.0.
|
||||||
|
|
||||||
## v0.103: API changes
|
## v0.103: API changes
|
||||||
|
|
||||||
### API: replace settings in GET /control/dns_info & POST /control/dns_config
|
### API: replace settings in GET /control/dns_info & POST /control/dns_config
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
'info':
|
'info':
|
||||||
'title': 'AdGuard Home'
|
'title': 'AdGuard Home'
|
||||||
'description': >
|
'description': >
|
||||||
AdGuard Home REST API. Our admin web interface is built on top of this REST
|
AdGuard Home REST-ish API. Our admin web interface is built on top of this
|
||||||
API.
|
REST-ish API.
|
||||||
'version': '0.104'
|
'version': '0.105'
|
||||||
'contact':
|
'contact':
|
||||||
'name': 'AdGuard Home'
|
'name': 'AdGuard Home'
|
||||||
'url': 'https://github.com/AdguardTeam/AdGuardHome'
|
'url': 'https://github.com/AdguardTeam/AdGuardHome'
|
||||||
|
@ -1259,11 +1259,26 @@
|
||||||
- 'FilteredBlockedService'
|
- 'FilteredBlockedService'
|
||||||
- 'ReasonRewrite'
|
- 'ReasonRewrite'
|
||||||
'filter_id':
|
'filter_id':
|
||||||
|
'deprecated': true
|
||||||
|
'description': >
|
||||||
|
In case if there's a rule applied to this DNS request, this is ID of
|
||||||
|
the filter list that the rule belongs to.
|
||||||
|
|
||||||
|
Deprecated: use `rules[*].filter_list_id` instead.
|
||||||
'type': 'integer'
|
'type': 'integer'
|
||||||
'rule':
|
'rule':
|
||||||
|
'deprecated': true
|
||||||
'type': 'string'
|
'type': 'string'
|
||||||
'example': '||example.org^'
|
'example': '||example.org^'
|
||||||
'description': 'Filtering rule applied to the request (if any)'
|
'description': >
|
||||||
|
Filtering rule applied to the request (if any).
|
||||||
|
|
||||||
|
Deprecated: use `rules[*].text` instead.
|
||||||
|
'rules':
|
||||||
|
'description': 'Applied rules.'
|
||||||
|
'type': 'array'
|
||||||
|
'items':
|
||||||
|
'$ref': '#/components/schemas/ResultRule'
|
||||||
'service_name':
|
'service_name':
|
||||||
'type': 'string'
|
'type': 'string'
|
||||||
'description': 'Set if reason=FilteredBlockedService'
|
'description': 'Set if reason=FilteredBlockedService'
|
||||||
|
@ -1610,15 +1625,27 @@
|
||||||
'question':
|
'question':
|
||||||
'$ref': '#/components/schemas/DnsQuestion'
|
'$ref': '#/components/schemas/DnsQuestion'
|
||||||
'filterId':
|
'filterId':
|
||||||
|
'deprecated': true
|
||||||
'type': 'integer'
|
'type': 'integer'
|
||||||
'example': 123123
|
'example': 123123
|
||||||
'description': >
|
'description': >
|
||||||
In case if there's a rule applied to this DNS request, this is ID of
|
In case if there's a rule applied to this DNS request, this is ID of
|
||||||
the filter that rule belongs to.
|
the filter list that the rule belongs to.
|
||||||
|
|
||||||
|
Deprecated: use `rules[*].filter_list_id` instead.
|
||||||
'rule':
|
'rule':
|
||||||
|
'deprecated': true
|
||||||
'type': 'string'
|
'type': 'string'
|
||||||
'example': '||example.org^'
|
'example': '||example.org^'
|
||||||
'description': 'Filtering rule applied to the request (if any)'
|
'description': >
|
||||||
|
Filtering rule applied to the request (if any).
|
||||||
|
|
||||||
|
Deprecated: use `rules[*].text` instead.
|
||||||
|
'rules':
|
||||||
|
'description': 'Applied rules.'
|
||||||
|
'type': 'array'
|
||||||
|
'items':
|
||||||
|
'$ref': '#/components/schemas/ResultRule'
|
||||||
'reason':
|
'reason':
|
||||||
'type': 'string'
|
'type': 'string'
|
||||||
'description': 'DNS filter status'
|
'description': 'DNS filter status'
|
||||||
|
@ -1668,6 +1695,22 @@
|
||||||
'anonymize_client_ip':
|
'anonymize_client_ip':
|
||||||
'type': 'boolean'
|
'type': 'boolean'
|
||||||
'description': "Anonymize clients' IP addresses"
|
'description': "Anonymize clients' IP addresses"
|
||||||
|
'ResultRule':
|
||||||
|
'description': 'Applied rule.'
|
||||||
|
'properties':
|
||||||
|
'filter_list_id':
|
||||||
|
'description': >
|
||||||
|
In case if there's a rule applied to this DNS request, this is ID of
|
||||||
|
the filter list that the rule belongs to.
|
||||||
|
'example': 123123
|
||||||
|
'format': 'int64'
|
||||||
|
'type': 'integer'
|
||||||
|
'text':
|
||||||
|
'description': >
|
||||||
|
The text of the filtering rule applied to the request (if any).
|
||||||
|
'example': '||example.org^'
|
||||||
|
'type': 'string'
|
||||||
|
'type': 'object'
|
||||||
'TlsConfig':
|
'TlsConfig':
|
||||||
'type': 'object'
|
'type': 'object'
|
||||||
'description': 'TLS configuration settings and status'
|
'description': 'TLS configuration settings and status'
|
||||||
|
|
|
@ -112,4 +112,4 @@ exit_on_output sh -c '
|
||||||
{ grep -e "defer" -e "_test\.go:" -v || exit 0; }
|
{ grep -e "defer" -e "_test\.go:" -v || exit 0; }
|
||||||
'
|
'
|
||||||
|
|
||||||
staticcheck --checks='all' ./...
|
staticcheck ./...
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
checks = ["all"]
|
||||||
|
initialisms = [
|
||||||
|
# See https://github.com/dominikh/go-tools/blob/master/config/config.go.
|
||||||
|
"inherit"
|
||||||
|
, "DHCP"
|
||||||
|
, "DOH"
|
||||||
|
, "DOQ"
|
||||||
|
, "DOT"
|
||||||
|
, "EDNS"
|
||||||
|
, "QUIC"
|
||||||
|
, "SDNS"
|
||||||
|
]
|
||||||
|
dot_import_whitelist = []
|
||||||
|
http_status_code_whitelist = []
|
Loading…
Reference in New Issue