Skip to content

Commit

Permalink
Pull request: all: export DNSResult.NetworkRules; imp api
Browse files Browse the repository at this point in the history
Merge in DNS/urlfilter from network-rules to master

* commit 'c67a1ef3e28b8a5c7ad15e8b3de566e5b5d151bd':
  all: imp docs
  all: export DNSResult.NetworkRules; imp api
  • Loading branch information
ainar-g committed Mar 10, 2022
2 parents 6f750ac + c67a1ef commit 26b0b0a
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 82 deletions.
60 changes: 33 additions & 27 deletions dnsengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@ type DNSEngine struct {
rulesStorage *filterlist.RuleStorage
}

// DNSResult - the return value of Match() function
// DNSResult is the result of matching a DNS filtering request.
type DNSResult struct {
NetworkRule *rules.NetworkRule // a network rule or nil
HostRulesV4 []*rules.HostRule // host rules for IPv4 or nil
HostRulesV6 []*rules.HostRule // host rules for IPv6 or nil

// networkRules are all matched network rules.
networkRules []*rules.NetworkRule
// NetworkRule is the matched network rule, if any. If it is nil,
// HostRulesV4 and HostRulesV6 may still contain matched hosts-file style
// rules.
NetworkRule *rules.NetworkRule

// HostRulesV4 and HostRulesV6 are the host rules with IPv4 and IPv6
// addresses respectively.
HostRulesV4 []*rules.HostRule
HostRulesV6 []*rules.HostRule

// NetworkRules are all matched network rules. These include unprocessed
// DNS rewrites, exception rules, and so on.
NetworkRules []*rules.NetworkRule
}

// DNSRequest represents a DNS query with associated metadata.
Expand Down Expand Up @@ -86,30 +93,29 @@ func NewDNSEngine(s *filterlist.RuleStorage) *DNSEngine {
return &d
}

// Match finds a matching rule for the specified hostname.
// Match finds a matching rule for the specified hostname. It returns true and
// the list of rules found or false and nil. A list of rules is returned when
// there are multiple host rules matching the same domain, for example:
//
// 192.168.0.1 example.local
// 2000::1 example.local
//
// It returns true and the list of rules found or false and nil.
// The list of rules can be found when there're multiple host rules matching the same domain.
// For instance:
// 192.168.0.1 example.local
// 2000::1 example.local
func (d *DNSEngine) Match(hostname string) (*DNSResult, bool) {
return d.MatchRequest(DNSRequest{Hostname: hostname, ClientIP: "0.0.0.0"})
func (d *DNSEngine) Match(hostname string) (res *DNSResult, matched bool) {
return d.MatchRequest(&DNSRequest{Hostname: hostname, ClientIP: "0.0.0.0"})
}

// MatchRequest matches the specified DNS request. The return parameter
// matched is true if the result has a basic network rule or some host
// rules.
// MatchRequest matches the specified DNS request. The return parameter matched
// is true if the result has a basic network rule or some host rules.
//
// For compatibility reasons, it is also false when there are DNS
// rewrite and other kinds of special network rules, so users who need
// those will need to ignore the matched return parameter and instead
// inspect the results of the corresponding DNSResult getters.
// For compatibility reasons, it is also false when there are DNS rewrite and
// other kinds of special network rules, so users who need those will need to
// ignore the matched return parameter and instead inspect the results of the
// corresponding DNSResult getters.
//
// TODO(ameshkov): return nil when there's no match. Currently, the logic is
// flawed because it analyzes the DNSResult even when matched is false and
// looks for $dnsrewrite rules.
func (d *DNSEngine) MatchRequest(dReq DNSRequest) (res *DNSResult, matched bool) {
// flawed because it analyzes the DNSResult even when matched is false and looks
// for $dnsrewrite rules.
func (d *DNSEngine) MatchRequest(dReq *DNSRequest) (res *DNSResult, matched bool) {
res = &DNSResult{}

if dReq.Hostname == "" {
Expand All @@ -122,9 +128,9 @@ func (d *DNSEngine) MatchRequest(dReq DNSRequest) (res *DNSResult, matched bool)
r.ClientName = dReq.ClientName
r.DNSType = dReq.DNSType

res.networkRules = d.networkEngine.MatchAll(r)
res.NetworkRules = d.networkEngine.MatchAll(r)

result := rules.NewMatchingResult(res.networkRules, nil)
result := rules.NewMatchingResult(res.NetworkRules, nil)
resultRule := result.GetBasicResult()
if resultRule != nil {
// Network rules always have higher priority.
Expand Down
82 changes: 41 additions & 41 deletions dnsengine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,63 +257,63 @@ func TestClientTags(t *testing.T) {
assert.NotNil(t, dnsEngine)

// global rule
res, ok := dnsEngine.MatchRequest(DNSRequest{Hostname: "host1", SortedClientTags: []string{"phone"}})
res, ok := dnsEngine.MatchRequest(&DNSRequest{Hostname: "host1", SortedClientTags: []string{"phone"}})
assert.True(t, ok)
assert.NotNil(t, res.NetworkRule)
assert.Equal(t, "||host1^", res.NetworkRule.Text())

// $ctag rule overrides global rule
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host1", SortedClientTags: []string{"pc"}})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host1", SortedClientTags: []string{"pc"}})
assert.True(t, ok)
assert.NotNil(t, res.NetworkRule)
assert.Equal(t, "||host1^$ctag=pc|printer", res.NetworkRule.Text())

// 1 tag matches
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host2", SortedClientTags: []string{"phone", "printer"}})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host2", SortedClientTags: []string{"phone", "printer"}})
assert.True(t, ok)
assert.NotNil(t, res.NetworkRule)
assert.Equal(t, "||host2^$ctag=pc|printer", res.NetworkRule.Text())

// tags don't match
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host2", SortedClientTags: []string{"phone"}})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host2", SortedClientTags: []string{"phone"}})
assert.False(t, ok)

// tags don't match
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host2", SortedClientTags: []string{}})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host2", SortedClientTags: []string{}})
assert.False(t, ok)

// 1 tag matches (exclusion)
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host3", SortedClientTags: []string{"phone", "printer"}})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host3", SortedClientTags: []string{"phone", "printer"}})
assert.True(t, ok)
assert.NotNil(t, res.NetworkRule)
assert.Equal(t, "||host3^$ctag=~pc|~router", res.NetworkRule.Text())

// 1 tag matches (exclusion)
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host4", SortedClientTags: []string{"phone", "router"}})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host4", SortedClientTags: []string{"phone", "router"}})
assert.True(t, ok)
assert.NotNil(t, res.NetworkRule)
assert.Equal(t, "||host4^$ctag=~pc|router", res.NetworkRule.Text())

// tags don't match (exclusion)
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host3", SortedClientTags: []string{"pc"}})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host3", SortedClientTags: []string{"pc"}})
assert.False(t, ok)

// tags don't match (exclusion)
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host4", SortedClientTags: []string{"pc", "router"}})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host4", SortedClientTags: []string{"pc", "router"}})
assert.False(t, ok)

// tags match but it's a $badfilter
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host5", SortedClientTags: []string{"pc"}})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host5", SortedClientTags: []string{"pc"}})
assert.False(t, ok)

// tags match and $badfilter rule disables global rule
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host6", SortedClientTags: []string{"pc"}})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host6", SortedClientTags: []string{"pc"}})
assert.True(t, ok)
assert.NotNil(t, res.NetworkRule)
assert.Equal(t, "||host6^$ctag=pc|printer", res.NetworkRule.Text())

// tags match (exclusion) but it's a $badfilter
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host7", SortedClientTags: []string{"phone"}})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host7", SortedClientTags: []string{"phone"}})
assert.False(t, ok)
}

Expand All @@ -334,75 +334,75 @@ func TestClient(t *testing.T) {
assert.NotNil(t, dnsEngine)

// match client IPv4
res, ok := dnsEngine.MatchRequest(DNSRequest{Hostname: "host0", ClientIP: "127.0.0.1"})
res, ok := dnsEngine.MatchRequest(&DNSRequest{Hostname: "host0", ClientIP: "127.0.0.1"})
assertMatchRuleText(t, rulesText[0], res, ok)

// not match client IPv4
_, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host0", ClientIP: "127.0.0.2"})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host0", ClientIP: "127.0.0.2"})
assert.False(t, ok)

// restricted client IPv4
_, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host1", ClientIP: "127.0.0.1"})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host1", ClientIP: "127.0.0.1"})
assert.False(t, ok)

// non-restricted client IPv4
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host1", ClientIP: "127.0.0.2"})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host1", ClientIP: "127.0.0.2"})
assertMatchRuleText(t, rulesText[1], res, ok)

// match client IPv6
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host2", ClientIP: "2001::c0:ffee"})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host2", ClientIP: "2001::c0:ffee"})
assertMatchRuleText(t, rulesText[2], res, ok)

// not match client IPv6
_, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host2", ClientIP: "2001::c0:ffef"})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host2", ClientIP: "2001::c0:ffef"})
assert.False(t, ok)

// restricted client IPv6
_, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host3", ClientIP: "2001::c0:ffee"})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host3", ClientIP: "2001::c0:ffee"})
assert.False(t, ok)

// non-restricted client IPv6
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host3", ClientIP: "2001::c0:ffef"})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host3", ClientIP: "2001::c0:ffef"})
assertMatchRuleText(t, rulesText[3], res, ok)

// match network IPv4
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host4", ClientIP: "127.0.0.254"})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host4", ClientIP: "127.0.0.254"})
assertMatchRuleText(t, rulesText[4], res, ok)

// not match network IPv4
_, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host4", ClientIP: "127.0.1.1"})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host4", ClientIP: "127.0.1.1"})
assert.False(t, ok)

// restricted network IPv4
_, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host5", ClientIP: "127.0.0.254"})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host5", ClientIP: "127.0.0.254"})
assert.False(t, ok)

// non-restricted network IPv4
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host5", ClientIP: "127.0.1.1"})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host5", ClientIP: "127.0.1.1"})
assertMatchRuleText(t, rulesText[5], res, ok)

// match network IPv6
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host6", ClientIP: "2001::c0:ff07"})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host6", ClientIP: "2001::c0:ff07"})
assertMatchRuleText(t, rulesText[6], res, ok)

// not match network IPv6
_, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host6", ClientIP: "2001::c0:feee"})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host6", ClientIP: "2001::c0:feee"})
assert.False(t, ok)

// restricted network IPv6
_, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host7", ClientIP: "2001::c0:ff07"})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host7", ClientIP: "2001::c0:ff07"})
assert.False(t, ok)

// non-restricted network IPv6
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host7", ClientIP: "2001::c0:feee"})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host7", ClientIP: "2001::c0:feee"})
assertMatchRuleText(t, rulesText[7], res, ok)

// match client name
res, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host8", ClientName: "Frank's laptop"})
res, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host8", ClientName: "Frank's laptop"})
assertMatchRuleText(t, rulesText[8], res, ok)

// not match client name
_, ok = dnsEngine.MatchRequest(DNSRequest{Hostname: "host8", ClientName: "Franks laptop"})
_, ok = dnsEngine.MatchRequest(&DNSRequest{Hostname: "host8", ClientName: "Franks laptop"})
assert.False(t, ok)
}

Expand Down Expand Up @@ -435,7 +435,7 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {
assert.NotNil(t, dnsEngine)

t.Run("simple", func(t *testing.T) {
r := DNSRequest{Hostname: "simple", DNSType: dns.TypeAAAA}
r := &DNSRequest{Hostname: "simple", DNSType: dns.TypeAAAA}
_, ok := dnsEngine.MatchRequest(r)
assert.True(t, ok)

Expand All @@ -445,7 +445,7 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {
})

t.Run("simple_case", func(t *testing.T) {
r := DNSRequest{Hostname: "simple_case", DNSType: dns.TypeAAAA}
r := &DNSRequest{Hostname: "simple_case", DNSType: dns.TypeAAAA}
_, ok := dnsEngine.MatchRequest(r)
assert.True(t, ok)

Expand All @@ -455,7 +455,7 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {
})

t.Run("reverse", func(t *testing.T) {
r := DNSRequest{Hostname: "reverse", DNSType: dns.TypeAAAA}
r := &DNSRequest{Hostname: "reverse", DNSType: dns.TypeAAAA}
_, ok := dnsEngine.MatchRequest(r)
assert.False(t, ok)

Expand All @@ -465,7 +465,7 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {
})

t.Run("multiple", func(t *testing.T) {
r := DNSRequest{Hostname: "multiple", DNSType: dns.TypeAAAA}
r := &DNSRequest{Hostname: "multiple", DNSType: dns.TypeAAAA}
_, ok := dnsEngine.MatchRequest(r)
assert.True(t, ok)

Expand All @@ -479,7 +479,7 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {
})

t.Run("multiple_reverse", func(t *testing.T) {
r := DNSRequest{
r := &DNSRequest{
Hostname: "multiple_reverse",
DNSType: dns.TypeAAAA,
}
Expand All @@ -498,7 +498,7 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {

t.Run("multiple_different", func(t *testing.T) {
// Should be the same as simple.
r := DNSRequest{
r := &DNSRequest{
Hostname: "multiple_different",
DNSType: dns.TypeAAAA,
}
Expand All @@ -516,7 +516,7 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {
})

t.Run("simple_client", func(t *testing.T) {
r := DNSRequest{
r := &DNSRequest{
Hostname: "simple_client",
DNSType: dns.TypeAAAA,
ClientIP: "127.0.0.1",
Expand All @@ -525,15 +525,15 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {
_, ok := dnsEngine.MatchRequest(r)
assert.True(t, ok)

r = DNSRequest{
r = &DNSRequest{
Hostname: "simple_client",
DNSType: dns.TypeAAAA,
ClientIP: "127.0.0.2",
}
_, ok = dnsEngine.MatchRequest(r)
assert.False(t, ok)

r = DNSRequest{
r = &DNSRequest{
Hostname: "simple_client",
DNSType: dns.TypeA,
ClientIP: "127.0.0.1",
Expand All @@ -543,7 +543,7 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {
})

t.Run("priority", func(t *testing.T) {
r := DNSRequest{
r := &DNSRequest{
Hostname: "priority",
DNSType: dns.TypeAAAA,
ClientIP: "127.0.0.1",
Expand All @@ -553,7 +553,7 @@ func TestDNSEngine_MatchRequest_dnsType(t *testing.T) {
assert.True(t, ok)
assert.Contains(t, res.NetworkRule.Text(), "dnstype=")

r = DNSRequest{
r = &DNSRequest{
Hostname: "priority",
DNSType: dns.TypeA,
ClientIP: "127.0.0.1",
Expand Down
Loading

0 comments on commit 26b0b0a

Please sign in to comment.