Skip to content

Commit

Permalink
Use full qualified client name as group name (#329)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xERR0R committed Feb 22, 2022
1 parent df9866f commit f9369d8
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 7 deletions.
10 changes: 8 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ in hosts format (YAML literal block scalar style). All Urls must be grouped to a
If a group has **only** whitelist entries -> this means only domains from this list are allowed, all other domains will
be blocked

!!! note
Please define also client group mapping, otherwise you black and whitelist definition will have no effect

#### Regex support

You can use regex to define patterns to block. A regex entry must start and end with the slash character (/). Some
Expand All @@ -282,8 +285,11 @@ group, which blocky adult sites.

Clients without a group assignment will use automatically the **default** group.

You can use the client name (see [Client name lookup](#client-name-lookup)), client's IP address or a client subnet as
CIDR notation.
You can use the client name (see [Client name lookup](#client-name-lookup)), client's IP address, client's full-qualified domain name
or a client subnet as CIDR notation.

If full-qualified domain name is used (for example "myclient.ddns.org"), blocky will try to resolve the IP address (A and AAAA records) of this domain.
If client's IP address matches with the result, the defined group will be used.

!!! example

Expand Down
12 changes: 11 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/0xERR0R/blocky
go 1.17

require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/alicebob/miniredis/v2 v2.18.0
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
github.com/avast/retry-go/v4 v4.0.3
Expand All @@ -22,6 +23,7 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.3.0
github.com/stretchr/testify v1.7.0
github.com/swaggo/swag v1.7.8
github.com/x-cray/logrus-prefixed-formatter v0.5.2
golang.org/x/net v0.0.0-20211209124913-491a49abca63
gopkg.in/yaml.v2 v2.4.0
Expand All @@ -32,12 +34,19 @@ require (
)

require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
Expand All @@ -52,7 +61,8 @@ require (
github.com/jackc/pgx/v4 v4.14.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.9 // indirect
Expand Down
47 changes: 47 additions & 0 deletions go.sum

Large diffs are not rendered by default.

75 changes: 72 additions & 3 deletions resolver/blocking_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"strings"
"time"

"github.com/0xERR0R/blocky/cache/expirationcache"

"github.com/hashicorp/go-multierror"

"github.com/0xERR0R/blocky/api"
Expand Down Expand Up @@ -83,6 +85,7 @@ type BlockingResolver struct {
clientGroupsBlock map[string][]string
redisClient *redis.Client
redisEnabled bool
fqdnIPCache expirationcache.ExpiringCache
}

// NewBlockingResolver returns a new configured instance of the resolver
Expand Down Expand Up @@ -142,6 +145,10 @@ func NewBlockingResolver(cfg config.BlockingConfig, redis *redis.Client) (Chaine
setupRedisEnabledSubscriber(res)
}

_ = evt.Bus().Subscribe(evt.ApplicationStarted, func(_ ...string) {
go res.initFQDNIPCache()
})

return res, nil
}

Expand Down Expand Up @@ -449,10 +456,20 @@ func (r *BlockingResolver) groupsToCheckForClient(request *model.Request) []stri
groups = append(groups, groupsByIP...)
}

// try CIDR
for cidr, groupsByCidr := range r.clientGroupsBlock {
if util.CidrContainsIP(cidr, request.ClientIP) {
for clientIdentifier, groupsByCidr := range r.clientGroupsBlock {
// try CIDR
if util.CidrContainsIP(clientIdentifier, request.ClientIP) {
groups = append(groups, groupsByCidr...)
} else if isFQDN(clientIdentifier) && r.fqdnIPCache != nil {
clIps, _ := r.fqdnIPCache.Get(clientIdentifier)
if clIps != nil {
ips := clIps.([]net.IP)
for _, ip := range ips {
if ip.Equal(request.ClientIP) {
groups = append(groups, groupsByCidr...)
}
}
}
}
}

Expand Down Expand Up @@ -539,3 +556,55 @@ func (b ipBlockHandler) handleBlock(question dns.Question, response *dns.Msg) {
b.fallbackHandler.handleBlock(question, response)
}
}

func (r *BlockingResolver) queryForFQIdentifierIPs(identifier string) (result []net.IP, ttl time.Duration) {
for _, mType := range []uint16{dns.TypeA, dns.TypeAAAA} {
prefixedLog := log.PrefixedLog("FQDNClientIdentifierCache")
resp, err := r.next.Resolve(&model.Request{
Req: util.NewMsgWithQuestion(identifier, mType),
Log: prefixedLog,
})

if err == nil && resp.Res.Rcode == dns.RcodeSuccess {
for _, rr := range resp.Res.Answer {
ttl = time.Duration(rr.Header().Ttl) * time.Second

switch v := rr.(type) {
case *dns.A:
result = append(result, v.A)
case *dns.AAAA:
result = append(result, v.AAAA)
}
}

prefixedLog.Debugf("resolved IPs '%v' for fq identifier '%s'", result, identifier)
}
}

return
}

func (r *BlockingResolver) initFQDNIPCache() {
identifiers := make([]string, 0)

for identifier := range r.clientGroupsBlock {
identifiers = append(identifiers, identifier)
}

r.fqdnIPCache = expirationcache.NewCache(expirationcache.WithCleanUpInterval(5*time.Second),
expirationcache.WithOnExpiredFn(func(key string) (val interface{}, ttl time.Duration) {
return r.queryForFQIdentifierIPs(key)
}))

for _, identifier := range identifiers {
if isFQDN(identifier) {
iPs, ttl := r.queryForFQIdentifierIPs(identifier)
r.fqdnIPCache.Put(identifier, iPs, ttl)
}
}
}

func isFQDN(in string) bool {
s := strings.Trim(in, ".")
return strings.Contains(s, ".")
}
40 changes: 40 additions & 0 deletions resolver/blocking_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ badcnamedomain.com`)
})
})

Describe("Blocking with full-qualified client name", func() {
BeforeEach(func() {
sutConfig = config.BlockingConfig{
BlockType: "ZEROIP",
BlockTTL: config.Duration(time.Minute),
BlackLists: map[string][]string{
"gr1": {group1File.Name()},
"gr2": {group2File.Name()},
},
ClientGroupsBlock: map[string][]string{
"default": {"gr1"},
"full.qualified.com": {"gr2"},
},
}

})

When("Full-qualified group name is used", func() {
It("bla", func() {
tmp, _ := NewBlockingResolver(sutConfig, nil)
sut = tmp.(*BlockingResolver)
sut.Next(&MockResolver{AnswerFn: func(t uint16, qName string) *dns.Msg {
if t == dns.TypeA && qName == "full.qualified.com." {
a, _ := util.NewMsgWithAnswer(qName, 60*60, dns.TypeA, "192.168.178.39")
return a
}
return nil
}})
sut.RefreshLists()
Bus().Publish(ApplicationStarted, "")
Eventually(func(g Gomega) {
resp, err = sut.Resolve(newRequestWithClient("blocked2.com.", dns.TypeA, "192.168.178.39", "client1"))
g.Expect(err).NotTo(HaveOccurred())
g.Expect(resp.Res.Answer).ShouldNot(BeNil())
g.Expect(resp.Res.Answer).Should(BeDNSRecord("blocked2.com.", dns.TypeA, 60, "0.0.0.0"))
}, "1s").Should(Succeed())
})
})
})

Describe("Blocking requests", func() {
var rType ResponseType
BeforeEach(func() {
Expand Down
30 changes: 30 additions & 0 deletions resolver/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@ import (
"github.com/stretchr/testify/mock"
)

type MockResolver struct {
AnswerFn func(t uint16, qName string) *dns.Msg
}

func (m *MockResolver) Resolve(req *model.Request) (*model.Response, error) {
for _, question := range req.Req.Question {
answer := m.AnswerFn(question.Qtype, question.Name)
if answer != nil {
return &model.Response{
Res: answer,
Reason: "",
RType: model.ResponseTypeRESOLVED,
}, nil
}
}

response := new(dns.Msg)
response.SetRcode(req.Req, dns.RcodeBadName)

return &model.Response{
Res: response,
Reason: "",
RType: model.ResponseTypeRESOLVED,
}, nil
}

func (m *MockResolver) Configuration() []string {
return []string{}
}

type resolverMock struct {
mock.Mock
NextResolver
Expand Down
2 changes: 1 addition & 1 deletion util/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func ExtractDomainOnly(in string) string {
// NewMsgWithQuestion creates new DNS message with question
func NewMsgWithQuestion(question string, mType uint16) *dns.Msg {
msg := new(dns.Msg)
msg.SetQuestion(question, mType)
msg.SetQuestion(dns.Fqdn(question), mType)

return msg
}
Expand Down

0 comments on commit f9369d8

Please sign in to comment.