2020-01-12 18:23:35 +01:00
|
|
|
package resolver
|
|
|
|
|
|
|
|
import (
|
2023-10-07 22:21:40 +02:00
|
|
|
"context"
|
2020-01-12 18:23:35 +01:00
|
|
|
"fmt"
|
|
|
|
"net"
|
2023-11-18 21:42:14 +01:00
|
|
|
"slices"
|
2020-01-12 18:23:35 +01:00
|
|
|
"sort"
|
|
|
|
"strings"
|
2022-05-06 22:34:08 +02:00
|
|
|
"sync"
|
2022-12-29 14:58:25 +01:00
|
|
|
"sync/atomic"
|
2020-04-08 23:03:07 +02:00
|
|
|
"time"
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2022-02-14 21:38:26 +01:00
|
|
|
"github.com/0xERR0R/blocky/cache/expirationcache"
|
2023-11-18 21:42:14 +01:00
|
|
|
"golang.org/x/exp/maps"
|
2022-02-14 21:38:26 +01:00
|
|
|
|
2021-10-13 22:45:32 +02:00
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
|
2021-08-25 22:06:34 +02:00
|
|
|
"github.com/0xERR0R/blocky/api"
|
|
|
|
"github.com/0xERR0R/blocky/config"
|
|
|
|
"github.com/0xERR0R/blocky/evt"
|
|
|
|
"github.com/0xERR0R/blocky/lists"
|
|
|
|
"github.com/0xERR0R/blocky/log"
|
2021-08-29 21:51:24 +02:00
|
|
|
"github.com/0xERR0R/blocky/model"
|
2022-01-19 22:03:41 +01:00
|
|
|
"github.com/0xERR0R/blocky/redis"
|
2021-08-25 22:06:34 +02:00
|
|
|
"github.com/0xERR0R/blocky/util"
|
2021-02-22 22:29:34 +01:00
|
|
|
|
2020-01-12 18:23:35 +01:00
|
|
|
"github.com/miekg/dns"
|
2021-02-22 22:29:34 +01:00
|
|
|
"github.com/sirupsen/logrus"
|
2020-01-12 18:23:35 +01:00
|
|
|
)
|
|
|
|
|
2022-05-10 09:09:50 +02:00
|
|
|
const defaultBlockingCleanUpInterval = 5 * time.Second
|
|
|
|
|
2023-11-21 22:27:51 +01:00
|
|
|
func createBlockHandler(cfg config.Blocking) (blockHandler, error) {
|
2021-11-05 23:00:54 +01:00
|
|
|
cfgBlockType := cfg.BlockType
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2021-11-05 23:00:54 +01:00
|
|
|
if strings.EqualFold(cfgBlockType, "NXDOMAIN") {
|
2022-05-06 22:34:08 +02:00
|
|
|
return nxDomainBlockHandler{}, nil
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2023-03-12 22:14:10 +01:00
|
|
|
blockTime := cfg.BlockTTL.SecondsU32()
|
2021-09-04 23:10:32 +02:00
|
|
|
|
2021-11-05 23:00:54 +01:00
|
|
|
if strings.EqualFold(cfgBlockType, "ZEROIP") {
|
2021-09-04 23:10:32 +02:00
|
|
|
return zeroIPBlockHandler{
|
|
|
|
BlockTimeSec: blockTime,
|
2022-05-06 22:34:08 +02:00
|
|
|
}, nil
|
2021-09-04 23:10:32 +02:00
|
|
|
}
|
|
|
|
|
2020-05-16 21:47:32 +02:00
|
|
|
var ips []net.IP
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2020-05-16 21:47:32 +02:00
|
|
|
for _, part := range strings.Split(cfgBlockType, ",") {
|
|
|
|
if ip := net.ParseIP(strings.TrimSpace(part)); ip != nil {
|
|
|
|
ips = append(ips, ip)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(ips) > 0 {
|
|
|
|
return ipBlockHandler{
|
2021-09-04 23:10:32 +02:00
|
|
|
destinations: ips,
|
|
|
|
BlockTimeSec: blockTime,
|
|
|
|
fallbackHandler: zeroIPBlockHandler{
|
|
|
|
BlockTimeSec: blockTime,
|
|
|
|
},
|
2022-05-06 22:34:08 +02:00
|
|
|
}, nil
|
2020-05-16 21:47:32 +02:00
|
|
|
}
|
|
|
|
|
2022-05-06 22:34:08 +02:00
|
|
|
return nil,
|
|
|
|
fmt.Errorf("unknown blockType '%s', please use one of: ZeroIP, NxDomain or specify destination IP address(es)",
|
|
|
|
cfgBlockType)
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2020-04-08 23:03:07 +02:00
|
|
|
type status struct {
|
2021-04-28 22:38:23 +02:00
|
|
|
// true: blocking of all groups is enabled
|
|
|
|
// false: blocking is disabled. Either all groups or only particular
|
|
|
|
enabled bool
|
|
|
|
disabledGroups []string
|
|
|
|
enableTimer *time.Timer
|
|
|
|
disableEnd time.Time
|
2022-05-06 22:34:08 +02:00
|
|
|
lock sync.RWMutex
|
2020-04-08 23:03:07 +02:00
|
|
|
}
|
|
|
|
|
2021-02-26 13:45:57 +01:00
|
|
|
// BlockingResolver checks request's question (domain name) against black and white lists
|
2020-01-12 18:23:35 +01:00
|
|
|
type BlockingResolver struct {
|
2023-11-21 22:27:51 +01:00
|
|
|
configurable[*config.Blocking]
|
2020-01-12 18:23:35 +01:00
|
|
|
NextResolver
|
2023-03-12 22:14:10 +01:00
|
|
|
typed
|
|
|
|
|
2021-02-08 21:57:59 +01:00
|
|
|
blacklistMatcher *lists.ListCache
|
|
|
|
whitelistMatcher *lists.ListCache
|
2020-05-16 21:47:32 +02:00
|
|
|
blockHandler blockHandler
|
2021-05-05 22:07:14 +02:00
|
|
|
whitelistOnlyGroups map[string]bool
|
2021-01-19 21:52:24 +01:00
|
|
|
status *status
|
2021-11-14 21:28:52 +01:00
|
|
|
clientGroupsBlock map[string][]string
|
2022-01-19 22:03:41 +01:00
|
|
|
redisClient *redis.Client
|
2023-03-15 20:32:35 +01:00
|
|
|
fqdnIPCache expirationcache.ExpiringCache[[]net.IP]
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2023-11-21 22:27:51 +01:00
|
|
|
func clientGroupsBlock(cfg config.Blocking) map[string][]string {
|
2023-09-30 21:19:47 +02:00
|
|
|
cgb := make(map[string][]string, len(cfg.ClientGroupsBlock))
|
|
|
|
|
|
|
|
for identifier, cfgGroups := range cfg.ClientGroupsBlock {
|
|
|
|
for _, ipart := range strings.Split(strings.ToLower(identifier), ",") {
|
|
|
|
existingGroups, found := cgb[ipart]
|
|
|
|
if found {
|
|
|
|
cgb[ipart] = append(existingGroups, cfgGroups...)
|
|
|
|
} else {
|
|
|
|
cgb[ipart] = cfgGroups
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cgb
|
|
|
|
}
|
|
|
|
|
2021-02-26 21:44:53 +01:00
|
|
|
// NewBlockingResolver returns a new configured instance of the resolver
|
2023-10-07 22:21:40 +02:00
|
|
|
func NewBlockingResolver(ctx context.Context,
|
2023-11-21 22:27:51 +01:00
|
|
|
cfg config.Blocking,
|
2023-10-07 22:21:40 +02:00
|
|
|
redis *redis.Client,
|
|
|
|
bootstrap *Bootstrap,
|
2022-12-29 14:58:25 +01:00
|
|
|
) (r *BlockingResolver, err error) {
|
2022-05-06 22:34:08 +02:00
|
|
|
blockHandler, err := createBlockHandler(cfg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-04-17 18:21:56 +02:00
|
|
|
downloader := lists.NewDownloader(cfg.Loading.Downloads, bootstrap.NewHTTPTransport())
|
|
|
|
|
2023-10-07 22:21:40 +02:00
|
|
|
blacklistMatcher, blErr := lists.NewListCache(ctx, lists.ListCacheTypeBlacklist,
|
|
|
|
cfg.Loading, cfg.BlackLists, downloader)
|
|
|
|
whitelistMatcher, wlErr := lists.NewListCache(ctx, lists.ListCacheTypeWhitelist,
|
|
|
|
cfg.Loading, cfg.WhiteLists, downloader)
|
2020-01-12 18:23:35 +01:00
|
|
|
whitelistOnlyGroups := determineWhitelistOnlyGroups(&cfg)
|
|
|
|
|
2022-04-22 22:12:35 +02:00
|
|
|
err = multierror.Append(err, blErr, wlErr).ErrorOrNil()
|
2023-04-17 18:21:56 +02:00
|
|
|
if err != nil {
|
2022-04-22 22:12:35 +02:00
|
|
|
return nil, err
|
2021-10-14 21:53:20 +02:00
|
|
|
}
|
|
|
|
|
2020-04-08 23:03:07 +02:00
|
|
|
res := &BlockingResolver{
|
2023-03-12 22:14:10 +01:00
|
|
|
configurable: withConfig(&cfg),
|
|
|
|
typed: withType("blocking"),
|
|
|
|
|
2020-05-16 21:47:32 +02:00
|
|
|
blockHandler: blockHandler,
|
2020-01-12 18:23:35 +01:00
|
|
|
blacklistMatcher: blacklistMatcher,
|
|
|
|
whitelistMatcher: whitelistMatcher,
|
|
|
|
whitelistOnlyGroups: whitelistOnlyGroups,
|
2021-01-19 21:52:24 +01:00
|
|
|
status: &status{
|
|
|
|
enabled: true,
|
|
|
|
enableTimer: time.NewTimer(0),
|
2020-04-08 23:03:07 +02:00
|
|
|
},
|
2023-09-30 21:19:47 +02:00
|
|
|
clientGroupsBlock: clientGroupsBlock(cfg),
|
2022-01-19 22:03:41 +01:00
|
|
|
redisClient: redis,
|
|
|
|
}
|
|
|
|
|
2023-10-07 22:21:40 +02:00
|
|
|
res.fqdnIPCache = expirationcache.NewCacheWithOnExpired[[]net.IP](ctx, expirationcache.Options{
|
2023-09-30 21:19:47 +02:00
|
|
|
CleanupInterval: defaultBlockingCleanUpInterval,
|
2023-11-19 21:47:50 +01:00
|
|
|
}, func(ctx context.Context, key string) (val *[]net.IP, ttl time.Duration) {
|
|
|
|
return res.queryForFQIdentifierIPs(ctx, key)
|
2023-09-30 21:19:47 +02:00
|
|
|
})
|
|
|
|
|
2023-03-12 22:14:10 +01:00
|
|
|
if res.redisClient != nil {
|
2023-11-19 21:47:50 +01:00
|
|
|
go res.redisSubscriber(ctx)
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
2020-04-08 23:03:07 +02:00
|
|
|
|
2023-09-18 11:42:49 +02:00
|
|
|
err = evt.Bus().SubscribeOnce(evt.ApplicationStarted, func(_ ...string) {
|
2023-11-19 21:47:50 +01:00
|
|
|
go res.initFQDNIPCache(ctx)
|
2022-02-14 21:38:26 +01:00
|
|
|
})
|
|
|
|
|
2023-09-18 11:42:49 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-10-14 21:53:20 +02:00
|
|
|
return res, nil
|
2020-04-08 23:03:07 +02:00
|
|
|
}
|
|
|
|
|
2023-11-19 21:47:50 +01:00
|
|
|
func (r *BlockingResolver) redisSubscriber(ctx context.Context) {
|
2024-01-27 23:47:01 +01:00
|
|
|
ctx, logger := r.log(ctx)
|
|
|
|
|
2023-11-19 21:47:50 +01:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case em := <-r.redisClient.EnabledChannel:
|
|
|
|
if em != nil {
|
2024-01-27 23:47:01 +01:00
|
|
|
logger.Debug("Received state from redis: ", em)
|
2023-11-19 21:47:50 +01:00
|
|
|
|
|
|
|
if em.State {
|
|
|
|
r.internalEnableBlocking()
|
|
|
|
} else {
|
2023-11-27 18:08:31 +01:00
|
|
|
err := r.internalDisableBlocking(ctx, em.Duration, em.Groups)
|
2023-11-19 21:47:50 +01:00
|
|
|
if err != nil {
|
2024-01-27 23:47:01 +01:00
|
|
|
logger.Warn("Blocking couldn't be disabled:", err)
|
2022-01-19 22:03:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-11-19 21:47:50 +01:00
|
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
2022-01-19 22:03:41 +01:00
|
|
|
}
|
2023-11-19 21:47:50 +01:00
|
|
|
}
|
2022-01-19 22:03:41 +01:00
|
|
|
}
|
|
|
|
|
2021-02-26 13:45:57 +01:00
|
|
|
// RefreshLists triggers the refresh of all black and white lists in the cache
|
2023-09-09 19:30:55 +02:00
|
|
|
func (r *BlockingResolver) RefreshLists() error {
|
|
|
|
var err *multierror.Error
|
|
|
|
|
|
|
|
err = multierror.Append(err, r.blacklistMatcher.Refresh())
|
|
|
|
err = multierror.Append(err, r.whitelistMatcher.Refresh())
|
|
|
|
|
|
|
|
return err.ErrorOrNil()
|
2021-02-08 21:57:59 +01:00
|
|
|
}
|
2021-02-26 13:45:57 +01:00
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
func (r *BlockingResolver) retrieveAllBlockingGroups() []string {
|
2023-11-18 21:42:14 +01:00
|
|
|
result := maps.Keys(r.cfg.BlackLists)
|
2021-04-28 22:38:23 +02:00
|
|
|
|
|
|
|
result = append(result, "default")
|
2023-11-18 21:42:14 +01:00
|
|
|
slices.Sort(result)
|
2021-04-28 22:38:23 +02:00
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-02-26 21:44:53 +01:00
|
|
|
// EnableBlocking enables the blocking against the blacklists
|
2023-11-27 18:08:31 +01:00
|
|
|
func (r *BlockingResolver) EnableBlocking(ctx context.Context) {
|
2022-01-19 22:03:41 +01:00
|
|
|
r.internalEnableBlocking()
|
|
|
|
|
2023-03-12 22:14:10 +01:00
|
|
|
if r.redisClient != nil {
|
2023-11-27 18:08:31 +01:00
|
|
|
r.redisClient.PublishEnabled(ctx, &redis.EnabledMessage{State: true})
|
2022-01-19 22:03:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *BlockingResolver) internalEnableBlocking() {
|
2021-02-04 21:59:41 +01:00
|
|
|
s := r.status
|
2022-05-06 22:34:08 +02:00
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
2021-02-04 21:59:41 +01:00
|
|
|
s.enableTimer.Stop()
|
|
|
|
s.enabled = true
|
2021-04-28 22:38:23 +02:00
|
|
|
s.disabledGroups = []string{}
|
2021-02-04 21:59:41 +01:00
|
|
|
|
|
|
|
evt.Bus().Publish(evt.BlockingEnabledEvent, true)
|
|
|
|
}
|
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
// DisableBlocking deactivates the blocking for a particular duration (or forever if 0).
|
2023-11-27 18:08:31 +01:00
|
|
|
func (r *BlockingResolver) DisableBlocking(ctx context.Context, duration time.Duration, disableGroups []string) error {
|
|
|
|
err := r.internalDisableBlocking(ctx, duration, disableGroups)
|
2023-03-12 22:14:10 +01:00
|
|
|
if err == nil && r.redisClient != nil {
|
2023-11-27 18:08:31 +01:00
|
|
|
r.redisClient.PublishEnabled(ctx, &redis.EnabledMessage{
|
2022-01-19 22:03:41 +01:00
|
|
|
State: false,
|
|
|
|
Duration: duration,
|
|
|
|
Groups: disableGroups,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-11-27 18:08:31 +01:00
|
|
|
func (r *BlockingResolver) internalDisableBlocking(ctx context.Context, duration time.Duration,
|
|
|
|
disableGroups []string,
|
|
|
|
) error {
|
2021-02-04 21:59:41 +01:00
|
|
|
s := r.status
|
2022-05-06 22:34:08 +02:00
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
2021-02-04 21:59:41 +01:00
|
|
|
s.enableTimer.Stop()
|
2022-05-06 22:34:08 +02:00
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
allBlockingGroups := r.retrieveAllBlockingGroups()
|
|
|
|
|
|
|
|
if len(disableGroups) == 0 {
|
|
|
|
s.disabledGroups = allBlockingGroups
|
|
|
|
} else {
|
|
|
|
for _, g := range disableGroups {
|
|
|
|
i := sort.SearchStrings(allBlockingGroups, g)
|
|
|
|
if !(i < len(allBlockingGroups) && allBlockingGroups[i] == g) {
|
|
|
|
return fmt.Errorf("group '%s' is unknown", g)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.disabledGroups = disableGroups
|
|
|
|
}
|
2021-02-04 21:59:41 +01:00
|
|
|
|
2022-05-06 22:34:08 +02:00
|
|
|
s.enabled = false
|
2021-02-04 21:59:41 +01:00
|
|
|
evt.Bus().Publish(evt.BlockingEnabledEvent, false)
|
|
|
|
|
|
|
|
s.disableEnd = time.Now().Add(duration)
|
|
|
|
|
|
|
|
if duration == 0 {
|
2021-12-20 22:37:32 +01:00
|
|
|
log.Log().Infof("disable blocking for group(s) '%s'", log.EscapeInput(strings.Join(s.disabledGroups, "; ")))
|
2021-02-04 21:59:41 +01:00
|
|
|
} else {
|
2021-12-20 22:37:32 +01:00
|
|
|
log.Log().Infof("disable blocking for %s for group(s) '%s'", duration,
|
|
|
|
log.EscapeInput(strings.Join(s.disabledGroups, "; ")))
|
2021-02-04 21:59:41 +01:00
|
|
|
s.enableTimer = time.AfterFunc(duration, func() {
|
2023-11-27 18:08:31 +01:00
|
|
|
r.EnableBlocking(ctx)
|
2021-02-25 23:36:39 +01:00
|
|
|
log.Log().Info("blocking enabled again")
|
2021-02-04 21:59:41 +01:00
|
|
|
})
|
|
|
|
}
|
2021-04-28 22:38:23 +02:00
|
|
|
|
|
|
|
return nil
|
2020-04-08 23:03:07 +02:00
|
|
|
}
|
|
|
|
|
2021-02-26 13:45:57 +01:00
|
|
|
// BlockingStatus returns the current blocking status
|
2021-02-04 21:59:41 +01:00
|
|
|
func (r *BlockingResolver) BlockingStatus() api.BlockingStatus {
|
2020-04-08 23:03:07 +02:00
|
|
|
var autoEnableDuration time.Duration
|
2022-05-06 22:34:08 +02:00
|
|
|
|
|
|
|
r.status.lock.RLock()
|
|
|
|
defer r.status.lock.RUnlock()
|
|
|
|
|
2020-04-08 23:03:07 +02:00
|
|
|
if !r.status.enabled && r.status.disableEnd.After(time.Now()) {
|
|
|
|
autoEnableDuration = time.Until(r.status.disableEnd)
|
|
|
|
}
|
|
|
|
|
2021-02-04 21:59:41 +01:00
|
|
|
return api.BlockingStatus{
|
2020-04-08 23:03:07 +02:00
|
|
|
Enabled: r.status.enabled,
|
2021-04-28 22:38:23 +02:00
|
|
|
DisabledGroups: r.status.disabledGroups,
|
2023-09-09 19:30:55 +02:00
|
|
|
AutoEnableInSec: int(autoEnableDuration.Seconds()),
|
2020-04-08 23:03:07 +02:00
|
|
|
}
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// returns groups, which have only whitelist entries
|
2023-11-21 22:27:51 +01:00
|
|
|
func determineWhitelistOnlyGroups(cfg *config.Blocking) (result map[string]bool) {
|
2022-03-17 22:30:21 +01:00
|
|
|
result = make(map[string]bool, len(cfg.WhiteLists))
|
2021-05-05 22:07:14 +02:00
|
|
|
|
2020-01-12 18:23:35 +01:00
|
|
|
for g, links := range cfg.WhiteLists {
|
|
|
|
if len(links) > 0 {
|
|
|
|
if _, found := cfg.BlackLists[g]; !found {
|
2021-05-05 22:07:14 +02:00
|
|
|
result[g] = true
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// sets answer and/or return code for DNS response, if request should be blocked
|
2021-02-22 22:29:34 +01:00
|
|
|
func (r *BlockingResolver) handleBlocked(logger *logrus.Entry,
|
2022-12-26 22:11:45 +01:00
|
|
|
request *model.Request, question dns.Question, reason string,
|
|
|
|
) (*model.Response, error) {
|
2020-02-13 18:12:59 +01:00
|
|
|
response := new(dns.Msg)
|
|
|
|
response.SetReply(request.Req)
|
|
|
|
|
2020-05-16 21:47:32 +02:00
|
|
|
r.blockHandler.handleBlock(question, response)
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2020-02-13 18:12:59 +01:00
|
|
|
logger.Debugf("blocking request '%s'", reason)
|
|
|
|
|
2021-09-09 22:57:05 +02:00
|
|
|
return &model.Response{Res: response, RType: model.ResponseTypeBLOCKED, Reason: reason}, nil
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2023-03-12 22:14:10 +01:00
|
|
|
// LogConfig implements `config.Configurable`.
|
|
|
|
func (r *BlockingResolver) LogConfig(logger *logrus.Entry) {
|
|
|
|
r.cfg.LogConfig(logger)
|
2022-12-02 02:42:17 +01:00
|
|
|
|
2023-03-12 22:14:10 +01:00
|
|
|
logger.Info("blacklist cache entries:")
|
|
|
|
log.WithIndent(logger, " ", r.blacklistMatcher.LogConfig)
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2023-03-12 22:14:10 +01:00
|
|
|
logger.Info("whitelist cache entries:")
|
|
|
|
log.WithIndent(logger, " ", r.whitelistMatcher.LogConfig)
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2021-05-05 22:07:14 +02:00
|
|
|
func (r *BlockingResolver) hasWhiteListOnlyAllowed(groupsToCheck []string) bool {
|
|
|
|
for _, group := range groupsToCheck {
|
|
|
|
if _, found := r.whitelistOnlyGroups[group]; found {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-11-19 21:47:50 +01:00
|
|
|
func (r *BlockingResolver) handleBlacklist(ctx context.Context, groupsToCheck []string,
|
2022-12-26 22:11:45 +01:00
|
|
|
request *model.Request, logger *logrus.Entry,
|
|
|
|
) (bool, *model.Response, error) {
|
2020-04-09 23:14:08 +02:00
|
|
|
logger.WithField("groupsToCheck", strings.Join(groupsToCheck, "; ")).Debug("checking groups for request")
|
2021-05-05 22:07:14 +02:00
|
|
|
whitelistOnlyAllowed := r.hasWhiteListOnlyAllowed(groupsToCheck)
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2020-04-09 23:14:08 +02:00
|
|
|
for _, question := range request.Req.Question {
|
|
|
|
domain := util.ExtractDomain(question)
|
|
|
|
logger := logger.WithField("domain", domain)
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2023-03-27 13:23:01 +02:00
|
|
|
if groups := r.matches(groupsToCheck, r.whitelistMatcher, domain); len(groups) > 0 {
|
|
|
|
logger.WithField("groups", groups).Debugf("domain is whitelisted")
|
2022-05-10 09:09:50 +02:00
|
|
|
|
2023-11-19 21:47:50 +01:00
|
|
|
resp, err := r.next.Resolve(ctx, request)
|
2022-05-10 09:09:50 +02:00
|
|
|
|
|
|
|
return true, resp, err
|
2020-04-09 23:14:08 +02:00
|
|
|
}
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2020-04-09 23:14:08 +02:00
|
|
|
if whitelistOnlyAllowed {
|
2022-05-10 09:09:50 +02:00
|
|
|
resp, err := r.handleBlocked(logger, request, question, "BLOCKED (WHITELIST ONLY)")
|
|
|
|
|
|
|
|
return true, resp, err
|
2020-04-09 23:14:08 +02:00
|
|
|
}
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2023-03-27 13:23:01 +02:00
|
|
|
if groups := r.matches(groupsToCheck, r.blacklistMatcher, domain); len(groups) > 0 {
|
|
|
|
resp, err := r.handleBlocked(logger, request, question, fmt.Sprintf("BLOCKED (%s)", strings.Join(groups, ",")))
|
2022-05-10 09:09:50 +02:00
|
|
|
|
|
|
|
return true, resp, err
|
2020-04-09 23:14:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-10 09:09:50 +02:00
|
|
|
return false, nil, nil
|
2020-04-09 23:14:08 +02:00
|
|
|
}
|
|
|
|
|
2021-02-26 21:44:53 +01:00
|
|
|
// Resolve checks the query against the blacklist and delegates to next resolver if domain is not blocked
|
2023-11-19 21:47:50 +01:00
|
|
|
func (r *BlockingResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
2024-01-27 23:47:01 +01:00
|
|
|
ctx, logger := r.log(ctx)
|
2020-04-09 23:14:08 +02:00
|
|
|
groupsToCheck := r.groupsToCheckForClient(request)
|
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
if len(groupsToCheck) > 0 {
|
2023-11-19 21:47:50 +01:00
|
|
|
handled, resp, err := r.handleBlacklist(ctx, groupsToCheck, request, logger)
|
2022-05-10 09:09:50 +02:00
|
|
|
if handled {
|
2020-04-09 23:14:08 +02:00
|
|
|
return resp, err
|
2020-02-13 18:12:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-19 21:47:50 +01:00
|
|
|
respFromNext, err := r.next.Resolve(ctx, request)
|
2020-02-13 18:12:59 +01:00
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
if err == nil && len(groupsToCheck) > 0 && respFromNext.Res != nil {
|
2020-02-13 18:12:59 +01:00
|
|
|
for _, rr := range respFromNext.Res.Answer {
|
|
|
|
entryToCheck, tName := extractEntryToCheckFromResponse(rr)
|
|
|
|
if len(entryToCheck) > 0 {
|
|
|
|
logger := logger.WithField("response_entry", entryToCheck)
|
|
|
|
|
2023-03-27 13:23:01 +02:00
|
|
|
if groups := r.matches(groupsToCheck, r.whitelistMatcher, entryToCheck); len(groups) > 0 {
|
|
|
|
logger.WithField("groups", groups).Debugf("%s is whitelisted", tName)
|
|
|
|
} else if groups := r.matches(groupsToCheck, r.blacklistMatcher, entryToCheck); len(groups) > 0 {
|
|
|
|
return r.handleBlocked(logger, request, request.Req.Question[0], fmt.Sprintf("BLOCKED %s (%s)", tName,
|
|
|
|
strings.Join(groups, ",")))
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-13 18:12:59 +01:00
|
|
|
return respFromNext, err
|
|
|
|
}
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2022-12-26 22:11:45 +01:00
|
|
|
func extractEntryToCheckFromResponse(rr dns.RR) (entryToCheck, tName string) {
|
2020-02-13 18:12:59 +01:00
|
|
|
switch v := rr.(type) {
|
|
|
|
case *dns.A:
|
|
|
|
entryToCheck = v.A.String()
|
|
|
|
tName = "IP"
|
|
|
|
case *dns.AAAA:
|
|
|
|
entryToCheck = strings.ToLower(v.AAAA.String())
|
|
|
|
tName = "IP"
|
|
|
|
case *dns.CNAME:
|
|
|
|
entryToCheck = util.ExtractDomainOnly(v.Target)
|
|
|
|
tName = "CNAME"
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
func (r *BlockingResolver) isGroupDisabled(group string) bool {
|
2022-05-06 22:34:08 +02:00
|
|
|
r.status.lock.RLock()
|
|
|
|
defer r.status.lock.RUnlock()
|
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
for _, g := range r.status.disabledGroups {
|
|
|
|
if g == group {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-01-12 18:23:35 +01:00
|
|
|
// returns groups which should be checked for client's request
|
2021-08-29 21:51:24 +02:00
|
|
|
func (r *BlockingResolver) groupsToCheckForClient(request *model.Request) []string {
|
2022-09-24 00:02:58 +02:00
|
|
|
r.status.lock.RLock()
|
|
|
|
defer r.status.lock.RUnlock()
|
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
var groups []string
|
2020-01-12 18:23:35 +01:00
|
|
|
// try client names
|
|
|
|
for _, cName := range request.ClientNames {
|
2021-11-14 21:28:52 +01:00
|
|
|
for blockGroup, groupsByName := range r.clientGroupsBlock {
|
2021-04-22 22:37:59 +02:00
|
|
|
if util.ClientNameMatchesGroupName(blockGroup, cName) {
|
2020-08-24 22:15:31 +02:00
|
|
|
groups = append(groups, groupsByName...)
|
|
|
|
}
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// try IP
|
2021-11-14 21:28:52 +01:00
|
|
|
groupsByIP, found := r.clientGroupsBlock[request.ClientIP.String()]
|
2020-01-12 18:23:35 +01:00
|
|
|
|
|
|
|
if found {
|
|
|
|
groups = append(groups, groupsByIP...)
|
|
|
|
}
|
|
|
|
|
2022-02-14 21:38:26 +01:00
|
|
|
for clientIdentifier, groupsByCidr := range r.clientGroupsBlock {
|
|
|
|
// try CIDR
|
|
|
|
if util.CidrContainsIP(clientIdentifier, request.ClientIP) {
|
2020-08-24 21:47:28 +02:00
|
|
|
groups = append(groups, groupsByCidr...)
|
2022-02-14 21:38:26 +01:00
|
|
|
} else if isFQDN(clientIdentifier) && r.fqdnIPCache != nil {
|
2023-03-15 20:32:35 +01:00
|
|
|
ips, _ := r.fqdnIPCache.Get(clientIdentifier)
|
|
|
|
if ips != nil {
|
|
|
|
for _, ip := range *ips {
|
2022-02-14 21:38:26 +01:00
|
|
|
if ip.Equal(request.ClientIP) {
|
|
|
|
groups = append(groups, groupsByCidr...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-24 21:47:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-12 18:23:35 +01:00
|
|
|
if len(groups) == 0 {
|
2021-04-28 22:38:23 +02:00
|
|
|
// return default
|
2021-11-14 21:28:52 +01:00
|
|
|
groups = r.clientGroupsBlock["default"]
|
2021-04-28 22:38:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var result []string
|
|
|
|
|
|
|
|
for _, g := range groups {
|
|
|
|
if !r.isGroupDisabled(g) {
|
|
|
|
result = append(result, g)
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
sort.Strings(result)
|
2020-01-12 18:23:35 +01:00
|
|
|
|
2021-04-28 22:38:23 +02:00
|
|
|
return result
|
2020-08-24 21:47:28 +02:00
|
|
|
}
|
|
|
|
|
2020-01-12 18:23:35 +01:00
|
|
|
func (r *BlockingResolver) matches(groupsToCheck []string, m lists.Matcher,
|
2022-12-26 22:11:45 +01:00
|
|
|
domain string,
|
2023-03-27 13:23:01 +02:00
|
|
|
) (group []string) {
|
2020-01-12 18:23:35 +01:00
|
|
|
if len(groupsToCheck) > 0 {
|
2023-03-27 13:23:01 +02:00
|
|
|
return m.Match(domain, groupsToCheck)
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2023-03-27 13:23:01 +02:00
|
|
|
return []string{}
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
2020-05-16 21:47:32 +02:00
|
|
|
|
|
|
|
type blockHandler interface {
|
|
|
|
handleBlock(question dns.Question, response *dns.Msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
type zeroIPBlockHandler struct {
|
2021-09-04 23:10:32 +02:00
|
|
|
BlockTimeSec uint32
|
2020-05-16 21:47:32 +02:00
|
|
|
}
|
|
|
|
|
2022-12-26 22:11:45 +01:00
|
|
|
type nxDomainBlockHandler struct{}
|
2020-05-16 21:47:32 +02:00
|
|
|
|
|
|
|
type ipBlockHandler struct {
|
|
|
|
destinations []net.IP
|
|
|
|
fallbackHandler blockHandler
|
2021-09-04 23:10:32 +02:00
|
|
|
BlockTimeSec uint32
|
2020-05-16 21:47:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b zeroIPBlockHandler) handleBlock(question dns.Question, response *dns.Msg) {
|
|
|
|
var zeroIP net.IP
|
|
|
|
|
|
|
|
switch question.Qtype {
|
|
|
|
case dns.TypeAAAA:
|
|
|
|
zeroIP = net.IPv6zero
|
2021-03-26 22:29:35 +01:00
|
|
|
case dns.TypeA:
|
2020-05-16 21:47:32 +02:00
|
|
|
zeroIP = net.IPv4zero
|
2021-03-26 22:29:35 +01:00
|
|
|
default:
|
|
|
|
response.Rcode = dns.RcodeNameError
|
2022-05-10 09:09:50 +02:00
|
|
|
|
2021-03-26 22:29:35 +01:00
|
|
|
return
|
2020-05-16 21:47:32 +02:00
|
|
|
}
|
|
|
|
|
2021-09-04 23:10:32 +02:00
|
|
|
rr, _ := util.CreateAnswerFromQuestion(question, zeroIP, b.BlockTimeSec)
|
2020-05-16 21:47:32 +02:00
|
|
|
|
|
|
|
response.Answer = append(response.Answer, rr)
|
|
|
|
}
|
|
|
|
|
2021-01-19 21:52:24 +01:00
|
|
|
func (b nxDomainBlockHandler) handleBlock(_ dns.Question, response *dns.Msg) {
|
2020-05-16 21:47:32 +02:00
|
|
|
response.Rcode = dns.RcodeNameError
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b ipBlockHandler) handleBlock(question dns.Question, response *dns.Msg) {
|
|
|
|
for _, ip := range b.destinations {
|
2021-09-04 23:10:32 +02:00
|
|
|
answer, _ := util.CreateAnswerFromQuestion(question, ip, b.BlockTimeSec)
|
2021-02-05 23:17:30 +01:00
|
|
|
|
2020-05-16 21:47:32 +02:00
|
|
|
if (question.Qtype == dns.TypeAAAA && ip.To4() == nil) || (question.Qtype == dns.TypeA && ip.To4() != nil) {
|
2021-02-05 23:17:30 +01:00
|
|
|
response.Answer = append(response.Answer, answer)
|
2020-05-16 21:47:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(response.Answer) == 0 {
|
|
|
|
// use fallback
|
|
|
|
b.fallbackHandler.handleBlock(question, response)
|
|
|
|
}
|
|
|
|
}
|
2022-02-14 21:38:26 +01:00
|
|
|
|
2023-11-19 21:47:50 +01:00
|
|
|
func (r *BlockingResolver) queryForFQIdentifierIPs(ctx context.Context, identifier string) (*[]net.IP, time.Duration) {
|
2024-01-27 23:47:01 +01:00
|
|
|
ctx, logger := r.logWith(ctx, func(logger *logrus.Entry) *logrus.Entry {
|
|
|
|
return log.WithPrefix(logger, "client_id_cache")
|
|
|
|
})
|
2022-04-22 22:12:35 +02:00
|
|
|
|
2023-03-15 20:32:35 +01:00
|
|
|
var result []net.IP
|
|
|
|
|
|
|
|
var ttl time.Duration
|
|
|
|
|
2022-04-22 22:12:35 +02:00
|
|
|
for _, qType := range []uint16{dns.TypeA, dns.TypeAAAA} {
|
2023-11-19 21:47:50 +01:00
|
|
|
resp, err := r.next.Resolve(ctx, &model.Request{
|
2022-04-22 22:12:35 +02:00
|
|
|
Req: util.NewMsgWithQuestion(identifier, dns.Type(qType)),
|
2022-02-14 21:38:26 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
if err == nil && resp.Res.Rcode == dns.RcodeSuccess {
|
|
|
|
for _, rr := range resp.Res.Answer {
|
2022-12-29 14:58:25 +01:00
|
|
|
ttl = time.Duration(atomic.LoadUint32(&rr.Header().Ttl)) * time.Second
|
2022-02-14 21:38:26 +01:00
|
|
|
|
|
|
|
switch v := rr.(type) {
|
|
|
|
case *dns.A:
|
|
|
|
result = append(result, v.A)
|
|
|
|
case *dns.AAAA:
|
|
|
|
result = append(result, v.AAAA)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:47:01 +01:00
|
|
|
if len(result) != 0 {
|
|
|
|
logger.WithFields(logrus.Fields{
|
|
|
|
"ips": result,
|
|
|
|
"client_id": identifier,
|
|
|
|
}).Debug("resolved client IPs")
|
|
|
|
}
|
|
|
|
|
2023-03-15 20:32:35 +01:00
|
|
|
return &result, ttl
|
2022-02-14 21:38:26 +01:00
|
|
|
}
|
|
|
|
|
2023-11-19 21:47:50 +01:00
|
|
|
func (r *BlockingResolver) initFQDNIPCache(ctx context.Context) {
|
2023-11-18 21:42:14 +01:00
|
|
|
identifiers := maps.Keys(r.clientGroupsBlock)
|
2022-02-14 21:38:26 +01:00
|
|
|
|
|
|
|
for _, identifier := range identifiers {
|
|
|
|
if isFQDN(identifier) {
|
2023-11-19 21:47:50 +01:00
|
|
|
iPs, ttl := r.queryForFQIdentifierIPs(ctx, identifier)
|
2022-02-14 21:38:26 +01:00
|
|
|
r.fqdnIPCache.Put(identifier, iPs, ttl)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isFQDN(in string) bool {
|
|
|
|
s := strings.Trim(in, ".")
|
2022-05-10 09:09:50 +02:00
|
|
|
|
2022-02-14 21:38:26 +01:00
|
|
|
return strings.Contains(s, ".")
|
|
|
|
}
|